MarketMindP / app.py
AronWolverine's picture
initial
56bd117
from flask import Flask, render_template, jsonify, request, redirect, url_for, session, send_from_directory
import requests
import os
from functools import wraps
import firebase_admin
from firebase_admin import credentials, auth
from flask import Flask, jsonify, render_template
from pymongo import MongoClient
import json
from static.pipelines.lstm_pipeline import run_lstm_prediction
from static.pipelines.lstm_n_pipeline import run_lstm_sentiment_prediction
import tensorflow as tf
import yfinance as yf
import requests
from flask import Response
import numpy as np
import pandas as pd
import traceback
from dotenv import load_dotenv
app = Flask(__name__)
app.secret_key = os.urandom(24)
load_dotenv()
# Firebase configuration
firebase_api_key = os.getenv("FIREBASE_API_KEY")
firebase_auth_domain = os.getenv("FIREBASE_AUTH_DOMAIN")
firebase_project = os.getenv("FIREBASE_PROJECT")
firebase_storage_bucket = os.getenv("FIREBASE_STORAGE_BUCKET")
firebase_messaging_sender_id = os.getenv("FIREBASE_MESSAGING_SENDER_ID")
firebase_app_id = os.getenv("FIREBASE_APP_ID")
firebase_measurement_id = os.getenv("FIREBASE_MEASUREMENT_ID")
# Example usage
# Use the MongoDB URI from the .env file
mongo_uri = os.getenv("MONGO_URI")
client = MongoClient(mongo_uri)
db = client["stock_news"]
companies_collection = db["nse50_companies"]
news_collection = db["moneyworks_company_news"]
# Firebase credentials
firebase_credentials_json = os.getenv("FIREBASE_CREDENTIALS_JSON")
if firebase_credentials_json:
firebase_credentials = json.loads(firebase_credentials_json)
cred = credentials.Certificate(firebase_credentials)
if not firebase_admin._apps: # Check if no app is already initialized
firebase_admin.initialize_app(cred)
def verify_firebase_token(token):
try:
decoded_token = auth.verify_id_token(token)
return decoded_token
except Exception as e:
return None
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Check if user is logged in (client-side auth check)
# For server-side verification, you would validate the Firebase token here
if 'user_logged_in' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
@app.route('/')
def home():
modelpath = url_for('static', filename='models/coins.glb')
return render_template("home.html", modelpath=modelpath)
@app.route('/login')
def login():
return render_template("login.html")
@app.route('/fundamentals')
def fundamentals():
return render_template("fundamentals.html")
@app.route('/movers')
def movers():
return render_template("movers.html")
@app.route('/news')
def news():
return render_template("news.html")
@app.route('/firebase-config')
def firebase_config():
return jsonify({
"apiKey": firebase_api_key,
"authDomain": firebase_auth_domain,
"projectId": firebase_project,
"storageBucket": firebase_storage_bucket,
"messagingSenderId": firebase_messaging_sender_id,
"appId": firebase_app_id,
"measurementId": firebase_measurement_id
})
@app.route("/get-companies")
def get_companies():
filtered = list(companies_collection.find({}, {"_id": 0, "Company Name": 1, "Yahoo Finance Ticker": 1}))
# Remove companies with duplicate tickers or ticker in ["NIFTY", "SENSEX"]
seen = set()
companies = []
for c in filtered:
ticker = c.get("Yahoo Finance Ticker", "").upper()
if not ticker or ticker in seen or ticker in {"^NSEI", "^BSESN"}:
continue
seen.add(ticker)
companies.append(c)
return jsonify(companies)
@app.route('/api/news-sentiment')
def api_news_sentiment():
ticker = request.args.get('ticker')
if not ticker:
return jsonify({'error': 'No ticker provided'}), 400
try:
# Fetch the latest sentiment from your database or sentiment analysis pipeline
news = news_collection.find_one({"yahoo_ticker": ticker}, sort=[("date", -1)])
if news and "sentiment" in news:
return jsonify({'sentiment': news["sentiment"], 'score': news.get("score", "N/A")})
else:
return jsonify({'sentiment': 'Neutral', 'score': 'N/A'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/predict')
def predict():
return render_template("predict.html")
@app.route('/privacy')
def privacy():
return render_template("privacy.html")
@app.route('/terms')
def terms():
return render_template("terms.html")
@app.route('/disclaimer')
def disclaimer():
return render_template("disclaimer.html")
@app.route('/predict-result', methods=['POST'])
def predict_result():
import tensorflow as tf
data = request.get_json()
prediction_type = data.get('type')
prediction_date = data.get('prediction_date')
epochs = int(data.get('epochs', 100))
try:
if prediction_type == 'historical-only' and prediction_date:
data['epochs'] = epochs
response = run_lstm_prediction(data)
elif prediction_type == 'news-sentiment' and prediction_date:
data['epochs'] = epochs
response = run_lstm_sentiment_prediction(data)
elif prediction_type == 'both' and prediction_date:
data['epochs'] = epochs
hist_result = run_lstm_prediction(data)
tf.keras.backend.clear_session()
sent_result = run_lstm_sentiment_prediction(data)
tf.keras.backend.clear_session()
if hasattr(hist_result, 'get_json'):
hist_result = hist_result.get_json()
if hasattr(sent_result, 'get_json'):
sent_result = sent_result.get_json()
return jsonify({
'historical': hist_result,
'sentiment': sent_result
})
else:
return jsonify({'error': 'Invalid prediction type'}), 400
except Exception as e:
print(traceback.format_exc())
return jsonify({'error': str(e)}), 500
finally:
tf.keras.backend.clear_session()
return response
@app.route('/api/lookup-symbol')
def lookup_symbol():
query = request.args.get('query', '').strip()
if not query:
return jsonify({'error': 'No query provided'}), 400
# Search by company name, symbol, company searched, or ticker (case-insensitive)
company = companies_collection.find_one({
"$or": [
{"Company Name": {"$regex": f"^{query}$", "$options": "i"}},
{"Yahoo Finance Ticker": {"$regex": f"^{query}$", "$options": "i"}},
{"Symbol": {"$regex": f"^{query}$", "$options": "i"}},
{"Company Searched": {"$regex": f"^{query}$", "$options": "i"}}
]
}, {"_id": 0, "Yahoo Finance Ticker": 1})
if not company:
return jsonify({'error': 'Company not found'}), 404
return jsonify({'symbol': company["Yahoo Finance Ticker"]})
@app.route('/api/historical')
def api_historical():
symbol = request.args.get('symbol')
start = request.args.get('start')
end = request.args.get('end')
if not symbol:
return jsonify({'error': 'No symbol provided'}), 400
try:
ticker = yf.Ticker(symbol)
if start and end:
history = ticker.history(start=start, end=end)
else:
history = ticker.history(period="1y")
if history.empty:
return jsonify({'error': f'No data found for symbol: {symbol}'}), 404
data = {
'history': history.reset_index().to_dict(orient='records')
}
return jsonify(data)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/fundamentals')
def api_fundamentals():
symbol = request.args.get('symbol')
if not symbol:
return jsonify({'error': 'No symbol provided'}), 400
try:
ticker = yf.Ticker(symbol)
# Always fetch 1y for risk metrics
risk_history = ticker.history(period="1y")
# Always fetch max for chart/history
full_history = ticker.history(period="max")
info = ticker.info
# Risk metrics from 1y history
if not risk_history.empty:
risk_history['Return'] = risk_history['Close'].pct_change()
volatility = float(risk_history['Return'].std() * np.sqrt(252))
var_95 = float(np.percentile(risk_history['Return'].dropna(), 5))
else:
volatility = None
var_95 = None
beta = info.get('beta')
def pct(val):
return round(val * 100, 2) if val is not None else None
data = {
'pe': info.get('trailingPE'),
'pb': info.get('priceToBook'),
'ps': info.get('priceToSalesTrailing12Months'),
'divYield': pct(info.get('dividendYield')),
'roe': pct(info.get('returnOnEquity')),
'roa': pct(info.get('returnOnAssets')),
'grossMargin': pct(info.get('grossMargins')),
'opMargin': pct(info.get('operatingMargins')),
'currentRatio': info.get('currentRatio'),
'quickRatio': info.get('quickRatio'),
'debtEquity': info.get('debtToEquity'),
'ebitdaMargin': info.get('ebitdaMargins'),
'volatility': volatility,
'beta': beta,
'var95': var_95,
}
# Always include full history for charting
if not full_history.empty:
data['history'] = full_history.reset_index().to_dict(orient='records')
return jsonify(data)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/news')
def api_news():
query = request.args.get('query', 'Indian stock market OR NSE OR Sensex OR Nifty')
rss_url = f"https://news.google.com/rss/search?q=Indian+finance+OR+economy+OR+RBI+OR+inflation+when:7d&hl=en-IN&gl=IN&ceid=IN:en"
r = requests.get(rss_url)
return Response(r.content, mimetype='application/xml')
@app.route('/api/market-movers')
def api_market_movers():
try:
# Get unique tickers from MongoDB
mongo_docs = list(companies_collection.find(
{"Yahoo Finance Ticker": {"$ne": None}},
{"Yahoo Finance Ticker": 1, "_id": 0}
))
tickers = list({doc["Yahoo Finance Ticker"] for doc in mongo_docs if "Yahoo Finance Ticker" in doc})
if not tickers:
return jsonify({"gainers": [], "losers": [], "error": "No tickers found in DB"}), 200
# Download last 3 days to buffer for non-trading days
data = yf.download(tickers, period="3d", interval="1d", group_by='ticker', progress=False, threads=True)
results = []
for ticker in tickers:
try:
df = data[ticker].dropna()
if df.shape[0] < 2:
continue
prev_close = df['Close'].iloc[-2]
last_close = df['Close'].iloc[-1]
pct_change = ((last_close - prev_close) / prev_close) * 100
# Only append if all values are valid numbers
if all(x is not None for x in [prev_close, last_close, pct_change]):
results.append({
'symbol': ticker,
'prev_close': round(prev_close, 2),
'last_close': round(last_close, 2),
'pct_change': round(pct_change, 2)
})
except Exception as e:
print(f"⚠️ Error processing {ticker}: {e}")
if not results:
return jsonify({"gainers": [], "losers": [], "error": "No price data available"}), 200
results_df = pd.DataFrame(results)
top_gainers = results_df.sort_values(by='pct_change', ascending=False).head(10).to_dict(orient='records')
top_losers = results_df.sort_values(by='pct_change', ascending=True).head(10).to_dict(orient='records')
return jsonify({"gainers": top_gainers, "losers": top_losers})
except Exception as e:
print("Market Movers API ERROR:", e)
return jsonify({"error": str(e)}), 500
results_df = pd.DataFrame(results)
top_gainers = results_df.sort_values(by='pct_change', ascending=False).head(5).to_dict(orient='records')
top_losers = results_df.sort_values(by='pct_change', ascending=True).head(5).to_dict(orient='records')
return jsonify({"gainers": top_gainers, "losers": top_losers})
except Exception as e:
print("Market Movers API ERROR:", e)
return jsonify({"error": str(e)}), 500
@app.route('/api/price')
def get_price():
ticker = request.args.get('ticker')
if not ticker:
return jsonify({'error': 'No ticker provided'}), 400
try:
stock = yf.Ticker(ticker)
price = stock.history(period="1d")['Close'][-1]
return jsonify({'price': round(float(price), 2)})
except Exception as e:
return jsonify({'error': f'Could not fetch price for {ticker}'}), 500
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.route('/api/auth/login', methods=['POST'])
def auth_login():
data = request.get_json()
# In a real app, you'd verify the Firebase token here
# token = data.get('token')
# user_data = verify_firebase_token(token)
# if not user_data:
# return jsonify({'success': False, 'message': 'Invalid token'}), 401
# Set session variable to mark user as logged in
session['user_logged_in'] = True
# You can store additional user info in session if needed
# session['user_id'] = data.get('uid')
# session['email'] = data.get('email')
return jsonify({'success': True})
# API route for logout
@app.route('/api/auth/logout', methods=['POST'])
def auth_logout():
# Clear session
session.pop('user_id', None)
session.clear()
redirect(url_for('home'))
return jsonify({'success': True})
# Error handlers
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
app.run(debug=True)