Spaces:
Sleeping
Sleeping
File size: 5,278 Bytes
dd86788 e79feeb dd86788 001d0c2 e79feeb dd86788 e79feeb dd86788 e79feeb dd86788 001d0c2 e79feeb 001d0c2 dd86788 e79feeb dd86788 e79feeb dd86788 18cd9cf 8d24420 dd86788 e79feeb dd86788 e79feeb dd86788 e79feeb dd86788 | 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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | from typing import List
import numpy as np
from fastapi import HTTPException
from tensorflow.keras.models import load_model
from datetime import datetime, timedelta
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 is_model_available() -> bool:
"""
Used by health endpoint.
Does NOT force model loading failure to crash the app.
"""
try:
get_model()
return True
except Exception:
return False
# Normalization
def _normalize_window(window: np.ndarray):
"""
window shape: (history_window, 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",
)
vol_mean = np.mean(window[:, 3])
if vol_mean <= 0:
vol_mean = 1.0 # safety guard
norm = np.zeros_like(window)
norm[:, 0] = (window[:, 0] / start_p) - 1
norm[:, 1] = window[:, 1] / 100.0
norm[:, 2] = window[:, 2] / start_p
norm[:, 3] = (window[:, 3] / vol_mean) - 1
norm[:, 4] = (window[:, 4] / start_p) - 1
return norm, start_p
# Prediction
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=(
f"Not enough clean data after indicators "
f"(required={settings.history_window}, got={len(df_tech)})"
),
)
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]
# 🔒 Safety clamp
pred_pct = float(np.clip(pred_pct, -0.2, 0.2))
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
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
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
|