Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import tensorflow as tf
|
3 |
+
import joblib
|
4 |
+
import logging
|
5 |
+
from fastapi import FastAPI, HTTPException
|
6 |
+
from pydantic import BaseModel
|
7 |
+
from typing import List
|
8 |
+
|
9 |
+
# FastAPI initialization
|
10 |
+
app = FastAPI(title="Universal Stock Prediction API")
|
11 |
+
|
12 |
+
# Request model structure
|
13 |
+
class StockPredictionInput(BaseModel):
|
14 |
+
stock_symbol: str
|
15 |
+
prices: List[List[float]]
|
16 |
+
|
17 |
+
# Constants
|
18 |
+
SEQ_LENGTH = 150
|
19 |
+
NUM_FEATURES = 10
|
20 |
+
|
21 |
+
# Load the trained model
|
22 |
+
try:
|
23 |
+
model = tf.keras.models.load_model("universal_stock_model_with_fundamentals.h5")
|
24 |
+
logging.info("✅ Model loaded successfully.")
|
25 |
+
except Exception as e:
|
26 |
+
logging.error(f"❌ Failed to load model: {e}")
|
27 |
+
raise RuntimeError("Model file not found or corrupted!")
|
28 |
+
|
29 |
+
@app.get("/")
|
30 |
+
def home():
|
31 |
+
return {"message": "Welcome to Stock Prediction API"}
|
32 |
+
|
33 |
+
@app.post("/predict")
|
34 |
+
async def predict_stock(data: StockPredictionInput):
|
35 |
+
stock = data.stock_symbol.upper()
|
36 |
+
logging.info(f"🔍 Processing request for stock: {stock}")
|
37 |
+
|
38 |
+
try:
|
39 |
+
scaler = joblib.load(f"scaler_{stock}.pkl")
|
40 |
+
logging.info(f"✅ Scaler loaded for stock: {stock}")
|
41 |
+
except Exception:
|
42 |
+
logging.warning(f"⚠️ Scaler not found for {stock}. Using identity transform.")
|
43 |
+
scaler = None
|
44 |
+
|
45 |
+
# Convert input to NumPy array and validate shape
|
46 |
+
prices_array = np.array(data.prices, dtype=np.float32)
|
47 |
+
if prices_array.shape != (SEQ_LENGTH, NUM_FEATURES):
|
48 |
+
raise HTTPException(status_code=400, detail=f"Expected input shape ({SEQ_LENGTH}, {NUM_FEATURES}) but got {prices_array.shape}")
|
49 |
+
|
50 |
+
# Scale first 9 features
|
51 |
+
features_to_scale = prices_array[:, :9]
|
52 |
+
extra_feature = prices_array[:, 9:]
|
53 |
+
|
54 |
+
if scaler:
|
55 |
+
features_scaled = scaler.transform(features_to_scale)
|
56 |
+
else:
|
57 |
+
features_scaled = features_to_scale
|
58 |
+
|
59 |
+
prices_scaled = np.concatenate([features_scaled, extra_feature], axis=1)
|
60 |
+
prices_scaled = np.expand_dims(prices_scaled, axis=0)
|
61 |
+
|
62 |
+
prediction = model.predict(prices_scaled)
|
63 |
+
predicted_price = float(prediction[0][0])
|
64 |
+
|
65 |
+
if scaler:
|
66 |
+
try:
|
67 |
+
dummy_input = np.zeros((1, 9))
|
68 |
+
dummy_input[0, 0] = predicted_price
|
69 |
+
inversed_values = scaler.inverse_transform(dummy_input)
|
70 |
+
predicted_price = inversed_values[0, 0]
|
71 |
+
except Exception as e:
|
72 |
+
logging.warning(f"⚠️ Error in inverse transformation: {e}")
|
73 |
+
|
74 |
+
return {"stock": stock, "predicted_price": round(predicted_price, 2)}
|