Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,49 +6,42 @@ from statsforecast import StatsForecast
|
|
| 6 |
from statsforecast.models import AutoARIMA
|
| 7 |
import nltk
|
| 8 |
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
| 9 |
-
import requests
|
| 10 |
|
| 11 |
-
#
|
| 12 |
try:
|
| 13 |
nltk.data.find('vader_lexicon')
|
| 14 |
except LookupError:
|
| 15 |
nltk.download('vader_lexicon')
|
| 16 |
sia = SentimentIntensityAnalyzer()
|
| 17 |
|
| 18 |
-
def
|
| 19 |
-
#
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
except:
|
| 25 |
-
return 50, "Unknown"
|
| 26 |
-
|
| 27 |
-
def get_news_sentiment(ticker):
|
| 28 |
-
# Fetch latest headlines via yfinance
|
| 29 |
-
stock = yf.Ticker(ticker)
|
| 30 |
-
news = stock.news[:5] # Get top 5 stories
|
| 31 |
-
if not news: return 0, "No recent news found."
|
| 32 |
|
|
|
|
|
|
|
|
|
|
| 33 |
scores = []
|
| 34 |
-
|
| 35 |
for article in news:
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
headlines_text += f"{sentiment_label} {title}<br>"
|
| 41 |
|
| 42 |
-
|
| 43 |
-
return
|
| 44 |
|
| 45 |
-
def
|
| 46 |
try:
|
| 47 |
-
#
|
| 48 |
df = yf.download(ticker, period="1y", multi_level_index=False)
|
| 49 |
-
if df.empty: return None, "⚠️ Symbol Error"
|
| 50 |
|
| 51 |
-
#
|
| 52 |
data = df.reset_index()[['Date', 'Close']]
|
| 53 |
data.columns = ['ds', 'y']
|
| 54 |
data['unique_id'] = ticker
|
|
@@ -56,54 +49,64 @@ def analyze_pro(ticker, horizon):
|
|
| 56 |
sf.fit(data)
|
| 57 |
forecast = sf.predict(h=horizon, level=[80])
|
| 58 |
|
| 59 |
-
#
|
| 60 |
-
sent_score,
|
| 61 |
-
mood_val, mood_label = get_market_mood()
|
| 62 |
|
| 63 |
-
#
|
| 64 |
-
# If Math is Bullish but Sentiment is Negative, we lower the 'Confidence'
|
| 65 |
math_move = ((forecast['AutoARIMA'].iloc[-1] - df['Close'].iloc[-1]) / df['Close'].iloc[-1]) * 100
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
#
|
|
|
|
|
|
|
|
|
|
| 69 |
color = "#00ff88" if math_move > 0 else "#ff4444"
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
-
<div style='background: #1e222d; padding: 15px; border-radius:
|
| 74 |
-
<h3 style='margin:0; color:{color};'>
|
| 75 |
-
<p style='font-
|
| 76 |
-
<p>AI Confidence: <b>{
|
| 77 |
-
<hr style='border:
|
| 78 |
-
<p
|
| 79 |
-
|
| 80 |
</div>
|
| 81 |
"""
|
| 82 |
-
|
| 83 |
-
# Charting
|
| 84 |
-
fig = go.Figure()
|
| 85 |
-
fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name='History', line=dict(color='#2962FF')))
|
| 86 |
-
fig.add_trace(go.Scatter(x=forecast['ds'], y=forecast['AutoARIMA'], name='AI Forecast', line=dict(color='#F23645')))
|
| 87 |
-
fig.update_layout(template='plotly_dark', paper_bgcolor='#131722', plot_bgcolor='#131722', margin=dict(l=0,r=0,t=0,b=0))
|
| 88 |
-
|
| 89 |
-
return fig, summary_html
|
| 90 |
|
| 91 |
except Exception as e:
|
| 92 |
-
return None, f"<div style='color:red;'>
|
| 93 |
|
| 94 |
-
# ---
|
| 95 |
-
with gr.Blocks(title="Quant-Node Pro
|
| 96 |
-
gr.HTML("<div style='text-align:center; padding:
|
| 97 |
|
| 98 |
with gr.Row():
|
| 99 |
with gr.Column(scale=1):
|
| 100 |
-
ticker = gr.Textbox(label="
|
| 101 |
horizon = gr.Slider(7, 90, value=30, label="Days to Forecast")
|
| 102 |
-
btn = gr.Button("
|
| 103 |
-
|
| 104 |
with gr.Column(scale=3):
|
| 105 |
-
|
| 106 |
|
| 107 |
-
btn.click(
|
| 108 |
|
| 109 |
demo.launch()
|
|
|
|
| 6 |
from statsforecast.models import AutoARIMA
|
| 7 |
import nltk
|
| 8 |
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
|
|
|
| 9 |
|
| 10 |
+
# Initialize Sentiment Engine
|
| 11 |
try:
|
| 12 |
nltk.data.find('vader_lexicon')
|
| 13 |
except LookupError:
|
| 14 |
nltk.download('vader_lexicon')
|
| 15 |
sia = SentimentIntensityAnalyzer()
|
| 16 |
|
| 17 |
+
def get_pro_analytics(df, ticker):
|
| 18 |
+
# 1. Volume Surge Alert (Compares current volume to 20-day average)
|
| 19 |
+
avg_vol = df['Volume'].tail(20).mean()
|
| 20 |
+
curr_vol = df['Volume'].iloc[-1]
|
| 21 |
+
surge_ratio = curr_vol / avg_vol
|
| 22 |
+
surge_alert = f"⚡ VOLUME SURGE: {surge_ratio:.1f}x" if surge_ratio > 2.0 else "Normal Volume"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
# 2. News Sentiment
|
| 25 |
+
stock = yf.Ticker(ticker)
|
| 26 |
+
news = stock.news[:3]
|
| 27 |
scores = []
|
| 28 |
+
headlines_html = ""
|
| 29 |
for article in news:
|
| 30 |
+
s = sia.polarity_scores(article['title'])['compound']
|
| 31 |
+
scores.append(s)
|
| 32 |
+
icon = "🟢" if s > 0.05 else "🔴" if s < -0.05 else "⚪"
|
| 33 |
+
headlines_html += f"<div style='font-size:11px; margin-bottom:4px;'>{icon} {article['title'][:60]}...</div>"
|
|
|
|
| 34 |
|
| 35 |
+
avg_sent = sum(scores)/len(scores) if scores else 0
|
| 36 |
+
return surge_alert, avg_sent, headlines_html
|
| 37 |
|
| 38 |
+
def analyze_full(ticker, horizon):
|
| 39 |
try:
|
| 40 |
+
# Data Acquisition
|
| 41 |
df = yf.download(ticker, period="1y", multi_level_index=False)
|
| 42 |
+
if df.empty: return None, "⚠️ Symbol Error"
|
| 43 |
|
| 44 |
+
# 1. Forecasting with StatsForecast
|
| 45 |
data = df.reset_index()[['Date', 'Close']]
|
| 46 |
data.columns = ['ds', 'y']
|
| 47 |
data['unique_id'] = ticker
|
|
|
|
| 49 |
sf.fit(data)
|
| 50 |
forecast = sf.predict(h=horizon, level=[80])
|
| 51 |
|
| 52 |
+
# 2. Extract Analytics
|
| 53 |
+
surge_text, sent_score, news_html = get_pro_analytics(df, ticker)
|
|
|
|
| 54 |
|
| 55 |
+
# 3. Confidence Calculation
|
|
|
|
| 56 |
math_move = ((forecast['AutoARIMA'].iloc[-1] - df['Close'].iloc[-1]) / df['Close'].iloc[-1]) * 100
|
| 57 |
+
# Correlation check: Is news sentiment in the same direction as the math?
|
| 58 |
+
is_aligned = (math_move > 0 and sent_score > 0) or (math_move < 0 and sent_score < 0)
|
| 59 |
+
confidence = 85 if is_aligned else 60
|
| 60 |
+
|
| 61 |
+
# 4. Charting with Confidence Zone
|
| 62 |
+
fig = go.Figure()
|
| 63 |
+
# History
|
| 64 |
+
fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name='Market History', line=dict(color='#2962FF', width=2)))
|
| 65 |
+
# Shaded Confidence Zone (80% Level)
|
| 66 |
+
fig.add_trace(go.Scatter(
|
| 67 |
+
x=pd.concat([forecast['ds'], forecast['ds'][::-1]]),
|
| 68 |
+
y=pd.concat([forecast['AutoARIMA-hi-80'], forecast['AutoARIMA-lo-80'][::-1]]),
|
| 69 |
+
fill='toself', fillcolor='rgba(242, 54, 69, 0.1)',
|
| 70 |
+
line=dict(color='rgba(255,255,255,0)'), name='AI Confidence Zone'
|
| 71 |
+
))
|
| 72 |
+
# Median Forecast
|
| 73 |
+
fig.add_trace(go.Scatter(x=forecast['ds'], y=forecast['AutoARIMA'], name='AI Target', line=dict(color='#F23645', width=3)))
|
| 74 |
|
| 75 |
+
fig.update_layout(template='plotly_dark', paper_bgcolor='#131722', plot_bgcolor='#131722',
|
| 76 |
+
margin=dict(l=10, r=10, t=10, b=10), legend=dict(orientation="h", y=1.1))
|
| 77 |
+
|
| 78 |
+
# 5. UI Summary
|
| 79 |
color = "#00ff88" if math_move > 0 else "#ff4444"
|
| 80 |
+
surge_color = "#FFD700" if "SURGE" in surge_text else "#787b86"
|
| 81 |
|
| 82 |
+
summary = f"""
|
| 83 |
+
<div style='background: #1e222d; padding: 15px; border-radius: 8px; border-top: 4px solid {color};'>
|
| 84 |
+
<h3 style='margin:0; color:{color};'>{'BULLISH' if math_move > 0 else 'BEARISH'} ({math_move:+.1f}%)</h3>
|
| 85 |
+
<p style='color:{surge_color}; font-weight:bold; margin:5px 0;'>{surge_text}</p>
|
| 86 |
+
<p style='font-size: 13px;'>AI Confidence: <b>{confidence}%</b></p>
|
| 87 |
+
<hr style='border:0.1px solid #363c4e;'>
|
| 88 |
+
<p style='font-size: 12px; font-weight:bold; margin-bottom:5px;'>LIVE SENTIMENT FEED:</p>
|
| 89 |
+
{news_html}
|
| 90 |
</div>
|
| 91 |
"""
|
| 92 |
+
return fig, summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
except Exception as e:
|
| 95 |
+
return None, f"<div style='color:red;'>Error: {str(e)}</div>"
|
| 96 |
|
| 97 |
+
# --- UI INTERFACE ---
|
| 98 |
+
with gr.Blocks(title="Quant-Node Pro Elite", theme=gr.themes.Default()) as demo:
|
| 99 |
+
gr.HTML("<div style='text-align:center; padding:15px; background:#131722;'><h1 style='color:#2962FF; margin:0;'>QUANT-NODE <span style='color:white;'>ELITE</span></h1></div>")
|
| 100 |
|
| 101 |
with gr.Row():
|
| 102 |
with gr.Column(scale=1):
|
| 103 |
+
ticker = gr.Textbox(label="Market Symbol", value="TSLA")
|
| 104 |
horizon = gr.Slider(7, 90, value=30, label="Days to Forecast")
|
| 105 |
+
btn = gr.Button("EXECUTE QUANT STRATEGY", variant="primary")
|
| 106 |
+
output_panel = gr.HTML(label="Intelligence Summary")
|
| 107 |
with gr.Column(scale=3):
|
| 108 |
+
plot = gr.Plot()
|
| 109 |
|
| 110 |
+
btn.click(analyze_full, [ticker, horizon], [plot, output_panel])
|
| 111 |
|
| 112 |
demo.launch()
|