StockPred-Backend / app /services /prediction.py
Anurag33Gaikwad's picture
Update app/services/prediction.py
dd86788 verified
Raw
History Blame
4.83 kB
from typing import List
import numpy as np
from fastapi import HTTPException
from tensorflow.keras.models import load_model
from app.config import get_settings
from app.indicators import classify_signal, classify_volatility
from app.services.market_data import get_enriched_history, last_n_candles
settings = get_settings()
_model = None
def get_model():
"""
Lazy-load model to avoid HF Space startup crashes
"""
global _model
if _model is None:
try:
_model = load_model(settings.model_path)
except Exception as e:
print(f"[Prediction] Failed to load model: {e}")
raise HTTPException(
status_code=500,
detail="Prediction model not available",
)
return _model
def _normalize_window(window: np.ndarray):
"""
window shape: (60, 5)
[Close, RSI, MACD, Log_Volume, MA50]
"""
start_p = window[0, 0]
if start_p == 0:
raise HTTPException(
status_code=500,
detail="Invalid price normalization window",
)
norm = np.zeros_like(window)
norm[:, 0] = (window[:, 0] / start_p) - 1 # Close
norm[:, 1] = window[:, 1] / 100.0 # RSI
norm[:, 2] = window[:, 2] / start_p # MACD
norm[:, 3] = (window[:, 3] / np.mean(window[:, 3])) - 1 # Volume
norm[:, 4] = (window[:, 4] / start_p) - 1 # MA50
return norm, start_p
def predict_path(ticker: str, days: int):
model = get_model()
if days > settings.max_forecast_days:
days = settings.max_forecast_days
df_raw, df_tech = get_enriched_history(ticker)
if len(df_tech) < settings.history_window:
raise HTTPException(
status_code=400,
detail="Insufficient data for prediction window",
)
features = df_tech[
["Close", "RSI", "MACD", "Log_Volume", "MA50"]
].values
last_window = features[-settings.history_window:]
current_price = float(last_window[-1, 0])
temp_window = last_window.copy()
forecast_points: List[dict] = []
for i in range(days):
norm, start_p = _normalize_window(temp_window)
pred_pct = model.predict(
norm.reshape(1, settings.history_window, 5),
verbose=0,
)[0][0]
next_price = float(start_p * (1 + pred_pct))
volatility = 0.015 * (i + 1)
lower_bound = float(next_price * (1 - volatility))
upper_bound = float(next_price * (1 + volatility))
forecast_points.append(
{
"day": i + 1,
"price": next_price,
"lower": lower_bound,
"upper": upper_bound,
}
)
new_row = temp_window[-1].copy()
new_row[0] = next_price
temp_window = np.vstack([temp_window[1:], new_row])
# Summary metrics
target_price = forecast_points[-1]["price"]
predicted_change = ((target_price - current_price) / current_price) * 100
vol_label = classify_volatility(df_raw)
signal = classify_signal(predicted_change)
rsi_latest = float(df_tech["RSI"].iloc[-1])
macd_latest = float(df_tech["MACD"].iloc[-1])
macd_label = "Bullish" if macd_latest >= 0 else "Bearish"
historical = last_n_candles(df_raw, settings.history_window)
prediction_payload = {
"ticker": ticker.upper(),
"currentPrice": current_price,
"targetPrice": target_price,
"predictedChange": predicted_change,
"signal": signal,
"rsi": rsi_latest,
"macd": macd_label,
"volatility": vol_label,
"historicalPrices": historical,
"predictedPrice": target_price,
"forecastData": [
{
"day": p["day"],
"price": p["price"],
"changePct": ((p["price"] - current_price) / current_price) * 100,
}
for p in forecast_points
],
}
# Chart payload
from datetime import datetime, timedelta
last_date = datetime.strptime(historical[-1]["date"], "%Y-%m-%d")
chart_forecast_payload = {
"ticker": ticker.upper(),
"points": [
{
"date": historical[-1]["date"],
"price": current_price,
"lower": current_price,
"upper": current_price,
}
],
}
for i, p in enumerate(forecast_points, start=1):
d = last_date + timedelta(days=i)
chart_forecast_payload["points"].append(
{
"date": d.strftime("%Y-%m-%d"),
"price": p["price"],
"lower": p["lower"],
"upper": p["upper"],
}
)
return prediction_payload, chart_forecast_payload