Ritvik
commited on
Commit
Β·
c615548
1
Parent(s):
bd5601f
Updated app
Browse files- .gradio/certificate.pem +31 -0
- .idea/.gitignore +8 -0
- .idea/Finance_Stock_Prediction_v1.iml +10 -0
- .idea/dataSources.xml +12 -0
- .idea/inspectionProfiles/profiles_settings.xml +6 -0
- .idea/misc.xml +7 -0
- .idea/modules.xml +8 -0
- .idea/vcs.xml +6 -0
- App.py +192 -0
- requirements.txt +9 -0
- sentiment_data.db +0 -0
.gradio/certificate.pem
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
-----BEGIN CERTIFICATE-----
|
2 |
+
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
|
31 |
+
-----END CERTIFICATE-----
|
.idea/.gitignore
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Default ignored files
|
2 |
+
/shelf/
|
3 |
+
/workspace.xml
|
4 |
+
# Editor-based HTTP Client requests
|
5 |
+
/httpRequests/
|
6 |
+
# Datasource local storage ignored files
|
7 |
+
/dataSources/
|
8 |
+
/dataSources.local.xml
|
.idea/Finance_Stock_Prediction_v1.iml
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<module type="PYTHON_MODULE" version="4">
|
3 |
+
<component name="NewModuleRootManager">
|
4 |
+
<content url="file://$MODULE_DIR$">
|
5 |
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
6 |
+
</content>
|
7 |
+
<orderEntry type="jdk" jdkName="Python 3.12 (Finance_Stock_Prediction_v1)" jdkType="Python SDK" />
|
8 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
9 |
+
</component>
|
10 |
+
</module>
|
.idea/dataSources.xml
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<project version="4">
|
3 |
+
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
4 |
+
<data-source source="LOCAL" name="sentiment_data" uuid="797dab22-bf41-4c84-8d62-7031ba070b53">
|
5 |
+
<driver-ref>sqlite.xerial</driver-ref>
|
6 |
+
<synchronize>true</synchronize>
|
7 |
+
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
8 |
+
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/sentiment_data.db</jdbc-url>
|
9 |
+
<working-dir>$ProjectFileDir$</working-dir>
|
10 |
+
</data-source>
|
11 |
+
</component>
|
12 |
+
</project>
|
.idea/inspectionProfiles/profiles_settings.xml
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<component name="InspectionProjectProfileManager">
|
2 |
+
<settings>
|
3 |
+
<option name="USE_PROJECT_PROFILE" value="false" />
|
4 |
+
<version value="1.0" />
|
5 |
+
</settings>
|
6 |
+
</component>
|
.idea/misc.xml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<project version="4">
|
3 |
+
<component name="Black">
|
4 |
+
<option name="sdkName" value="Python 3.9 (pythonProject)" />
|
5 |
+
</component>
|
6 |
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Finance_Stock_Prediction_v1)" project-jdk-type="Python SDK" />
|
7 |
+
</project>
|
.idea/modules.xml
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<project version="4">
|
3 |
+
<component name="ProjectModuleManager">
|
4 |
+
<modules>
|
5 |
+
<module fileurl="file://$PROJECT_DIR$/.idea/Finance_Stock_Prediction_v1.iml" filepath="$PROJECT_DIR$/.idea/Finance_Stock_Prediction_v1.iml" />
|
6 |
+
</modules>
|
7 |
+
</component>
|
8 |
+
</project>
|
.idea/vcs.xml
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
<project version="4">
|
3 |
+
<component name="VcsDirectoryMappings">
|
4 |
+
<mapping directory="" vcs="Git" />
|
5 |
+
</component>
|
6 |
+
</project>
|
App.py
ADDED
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
from bs4 import BeautifulSoup
|
4 |
+
import pandas as pd
|
5 |
+
import plotly.express as px
|
6 |
+
import datetime
|
7 |
+
import time
|
8 |
+
import nltk
|
9 |
+
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
10 |
+
from textblob import TextBlob
|
11 |
+
import ssl
|
12 |
+
import certifi
|
13 |
+
import sqlite3
|
14 |
+
|
15 |
+
# Configure SSL to use certifi certificates
|
16 |
+
ssl._create_default_https_context = lambda: ssl.create_default_context(cafile=certifi.where())
|
17 |
+
|
18 |
+
# Download VADER lexicon
|
19 |
+
try:
|
20 |
+
nltk.download('vader_lexicon')
|
21 |
+
print("β
VADER lexicon downloaded successfully!")
|
22 |
+
except Exception as e:
|
23 |
+
print(f"β Error downloading VADER lexicon: {str(e)}")
|
24 |
+
raise
|
25 |
+
|
26 |
+
# Initialize VADER sentiment analyzer
|
27 |
+
sia = SentimentIntensityAnalyzer()
|
28 |
+
|
29 |
+
FINVIZ_URL = 'https://finviz.com/quote.ashx?t='
|
30 |
+
session = requests.Session()
|
31 |
+
session.headers.update({
|
32 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
33 |
+
})
|
34 |
+
|
35 |
+
|
36 |
+
def get_news(ticker):
|
37 |
+
""" Fetch stock news from FinViz with error handling """
|
38 |
+
try:
|
39 |
+
url = FINVIZ_URL + ticker
|
40 |
+
for attempt in range(3):
|
41 |
+
response = session.get(url)
|
42 |
+
if response.status_code == 200:
|
43 |
+
break
|
44 |
+
time.sleep(5)
|
45 |
+
|
46 |
+
if response.status_code != 200:
|
47 |
+
return None, f"β Error: Received status code {response.status_code}"
|
48 |
+
|
49 |
+
html = BeautifulSoup(response.text, "html.parser")
|
50 |
+
news_table = html.find('table', class_='fullview-news-outer')
|
51 |
+
|
52 |
+
if not news_table:
|
53 |
+
return None, "β News table not found!"
|
54 |
+
|
55 |
+
return news_table, None
|
56 |
+
except Exception as e:
|
57 |
+
return None, f"β Error fetching stock news: {str(e)}"
|
58 |
+
|
59 |
+
|
60 |
+
def parse_news(news_table):
|
61 |
+
""" Extracts and parses stock news headlines from FinViz """
|
62 |
+
parsed_news = []
|
63 |
+
today_string = datetime.datetime.today().strftime('%Y-%m-%d')
|
64 |
+
|
65 |
+
for row in news_table.find_all('tr'):
|
66 |
+
try:
|
67 |
+
news_container = row.find('div', class_='news-link-container')
|
68 |
+
link = news_container.find('a', class_='tab-link-news') if news_container else row.find('a')
|
69 |
+
|
70 |
+
if not link or not link.get_text().strip():
|
71 |
+
continue
|
72 |
+
text = link.get_text().strip()
|
73 |
+
|
74 |
+
date_td = row.find('td', align='right')
|
75 |
+
date_scrape = date_td.text.strip().split() if date_td and date_td.text.strip() else []
|
76 |
+
if len(date_scrape) == 1:
|
77 |
+
date, time_ = today_string, date_scrape[0]
|
78 |
+
else:
|
79 |
+
date, time_ = datetime.datetime.strptime(date_scrape[0], '%b-%d-%y').strftime('%Y-%m-%d'), date_scrape[
|
80 |
+
1]
|
81 |
+
|
82 |
+
time_ = datetime.datetime.strptime(time_, '%I:%M%p').strftime('%H:%M')
|
83 |
+
parsed_news.append([date, time_, text])
|
84 |
+
except Exception:
|
85 |
+
continue
|
86 |
+
|
87 |
+
df = pd.DataFrame(parsed_news, columns=['date', 'time', 'headline'])
|
88 |
+
df['datetime'] = pd.to_datetime(df['date'] + ' ' + df['time'], format="%Y-%m-%d %H:%M", errors='coerce')
|
89 |
+
return df.dropna(subset=['datetime'])
|
90 |
+
|
91 |
+
|
92 |
+
def analyze_sentiment(text):
|
93 |
+
""" Combines VADER and TextBlob for better sentiment accuracy """
|
94 |
+
vader_score = sia.polarity_scores(text)['compound']
|
95 |
+
textblob_score = TextBlob(text).sentiment.polarity # -1 to 1 range
|
96 |
+
return (vader_score + textblob_score) / 2 # Averaging both
|
97 |
+
|
98 |
+
|
99 |
+
def score_news(df):
|
100 |
+
""" Applies sentiment analysis using both VADER and TextBlob """
|
101 |
+
df['sentiment_score'] = df['headline'].apply(analyze_sentiment)
|
102 |
+
return df[['datetime', 'headline', 'sentiment_score']]
|
103 |
+
|
104 |
+
|
105 |
+
def save_to_db(df, ticker):
|
106 |
+
""" Stores sentiment analysis results in SQLite for historical tracking """
|
107 |
+
conn = sqlite3.connect("sentiment_data.db")
|
108 |
+
df.to_sql(f"{ticker}_news", conn, if_exists="append", index=False)
|
109 |
+
conn.close()
|
110 |
+
|
111 |
+
|
112 |
+
def plot_sentiment(df, ticker, interval):
|
113 |
+
""" Generates sentiment trend plots with correct filtering """
|
114 |
+
if df.empty:
|
115 |
+
return None
|
116 |
+
|
117 |
+
df['datetime'] = pd.to_datetime(df['datetime'])
|
118 |
+
df = df.set_index('datetime')
|
119 |
+
|
120 |
+
# β
Resample only within the available range
|
121 |
+
df = df.resample(interval).mean(numeric_only=True).dropna()
|
122 |
+
|
123 |
+
# β
Use rolling average to smooth the graph
|
124 |
+
df['rolling_avg'] = df['sentiment_score'].rolling(5, min_periods=1).mean()
|
125 |
+
|
126 |
+
fig = px.line(df, x=df.index, y=['sentiment_score', 'rolling_avg'],
|
127 |
+
labels={"value": "Sentiment Score"},
|
128 |
+
title=f"{ticker} {interval.capitalize()} Sentiment Trends")
|
129 |
+
return fig
|
130 |
+
|
131 |
+
|
132 |
+
def analyze_stock_sentiment(ticker, days):
|
133 |
+
""" Fetches news, analyzes sentiment, and filters by user-selected date range. """
|
134 |
+
if not ticker:
|
135 |
+
return "β Please enter a stock ticker!", None, None, None
|
136 |
+
|
137 |
+
print(f"π
Selected Days: {days}") # Debugging
|
138 |
+
|
139 |
+
news_table, error = get_news(ticker.upper())
|
140 |
+
if error:
|
141 |
+
return error, None, None, None
|
142 |
+
|
143 |
+
df_news = parse_news(news_table)
|
144 |
+
|
145 |
+
# β
Convert `days` to an integer and filter news based on the correct time range
|
146 |
+
days = int(days)
|
147 |
+
today = datetime.datetime.today()
|
148 |
+
start_date = today - datetime.timedelta(days=days)
|
149 |
+
|
150 |
+
print(f"π Filtering News from {start_date} to {today}") # Debugging
|
151 |
+
|
152 |
+
df_news['datetime'] = pd.to_datetime(df_news['datetime'])
|
153 |
+
df_news = df_news[df_news['datetime'] >= start_date]
|
154 |
+
|
155 |
+
if df_news.empty:
|
156 |
+
return f"β οΈ No news found for {ticker.upper()} in the last {days} days.", None, None, None
|
157 |
+
|
158 |
+
df_scored = score_news(df_news).sort_values(by="datetime", ascending=False)
|
159 |
+
|
160 |
+
print(f"π Filtered News Count: {len(df_scored)}") # Debugging
|
161 |
+
|
162 |
+
save_to_db(df_scored, ticker.upper())
|
163 |
+
|
164 |
+
fig_hourly = plot_sentiment(df_scored, ticker, interval='h')
|
165 |
+
fig_daily = plot_sentiment(df_scored, ticker, interval='D')
|
166 |
+
|
167 |
+
return f"β
Analysis for {ticker.upper()} (Last {days} Days) Complete!", df_scored, fig_hourly, fig_daily
|
168 |
+
|
169 |
+
|
170 |
+
# Gradio Interface
|
171 |
+
with gr.Blocks(title="π Stock News Sentiment Analyzer") as iface:
|
172 |
+
with gr.Row():
|
173 |
+
gr.Markdown("## πStock News Sentiment Analyzer")
|
174 |
+
gr.Markdown("Analyze stock news sentiment using VADER and TextBlob.")
|
175 |
+
|
176 |
+
ticker_dropdown = gr.Dropdown(choices=["AAPL", "TSLA", "AMZN", "MSFT"], label="Stock Ticker")
|
177 |
+
days_slider = gr.Slider(minimum=1, maximum=30, step=1, label="Days of News History", value=7, interactive=True)
|
178 |
+
status = gr.Textbox(label="Status", interactive=False)
|
179 |
+
|
180 |
+
with gr.Row():
|
181 |
+
submit_btn = gr.Button("Analyze", variant="primary")
|
182 |
+
clear_btn = gr.Button("Clear", variant="secondary")
|
183 |
+
|
184 |
+
table = gr.Dataframe(label="Sentiment Analysis", interactive=False)
|
185 |
+
hourly_plot = gr.Plot(label="Hourly Sentiment Scores")
|
186 |
+
daily_plot = gr.Plot(label="Daily Sentiment Scores")
|
187 |
+
|
188 |
+
submit_btn.click(fn=analyze_stock_sentiment, inputs=[ticker_dropdown, days_slider],
|
189 |
+
outputs=[status, table, hourly_plot, daily_plot])
|
190 |
+
clear_btn.click(fn=lambda: ("", None, None, None), inputs=None, outputs=[status, table, hourly_plot, daily_plot])
|
191 |
+
|
192 |
+
iface.launch(share=True)
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
requests
|
3 |
+
beautifulsoup4
|
4 |
+
pandas
|
5 |
+
plotly
|
6 |
+
nltk
|
7 |
+
textblob
|
8 |
+
certifi
|
9 |
+
sqlite3
|
sentiment_data.db
ADDED
Binary file (303 kB). View file
|
|