Spaces:
Running
Running
mtotowajemo0
commited on
Commit
Β·
f045566
1
Parent(s):
30ab83e
Moved Streamlit app files to root directory
Browse files- .gitignore +16 -0
- LICENSE +21 -0
- README.md +57 -13
- app.py +293 -0
- nifty-news-analysis +0 -1
- requirements.txt +6 -0
.gitignore
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
pycache/
|
3 |
+
*.pyc
|
4 |
+
*.pyo
|
5 |
+
*.pyd
|
6 |
+
.Python
|
7 |
+
env/
|
8 |
+
venv/
|
9 |
+
.env
|
10 |
+
*.log
|
11 |
+
*.cache
|
12 |
+
*.DS_Store
|
13 |
+
init_and_push.txt
|
14 |
+
|
15 |
+
init_and_push.ps1
|
16 |
+
*.tmp
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2025 mtotowajemo0
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
CHANGED
@@ -1,13 +1,57 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# NIFTY 50 News Analysis
|
2 |
+
|
3 |
+
## Overview
|
4 |
+
This project is a Streamlit-based web application that analyzes news sentiment for companies in the NIFTY 50 index, categorized by sectors. It fetches news articles using the NewsAPI, summarizes them using the T5 model, and performs sentiment analysis using DistilBERT. The app provides insights into sector and company sentiment to guide investment decisions.
|
5 |
+
|
6 |
+
## Features
|
7 |
+
- **Sector Selection**: Choose from NIFTY 50 sectors (e.g., Financials, Healthcare).
|
8 |
+
- **Time Frame Analysis**: Analyze news from different periods (1D, 5D, 1M, 6M, YTD, 1Y, 5Y).
|
9 |
+
- **Sentiment Analysis**: Summarizes news and classifies sentiment as Positive, Negative, or Neutral.
|
10 |
+
- **Investment Insights**: Provides sentiment scores and recommendations for companies.
|
11 |
+
- **Interactive UI**: Built with Streamlit, featuring a user-friendly interface with tables and visualizations.
|
12 |
+
|
13 |
+
## Installation
|
14 |
+
1. Clone the repository:
|
15 |
+
```bash
|
16 |
+
git clone https://github.com/mtotowajemo0/nifty-news-analysis.git
|
17 |
+
```
|
18 |
+
2. Navigate to the project directory:
|
19 |
+
```bash
|
20 |
+
cd nifty-news-analysis
|
21 |
+
```
|
22 |
+
3. Install dependencies:
|
23 |
+
```bash
|
24 |
+
pip install -r requirements.txt
|
25 |
+
```
|
26 |
+
|
27 |
+
## Requirements
|
28 |
+
- Python 3.8+
|
29 |
+
- Libraries listed in `requirements.txt`:
|
30 |
+
- streamlit
|
31 |
+
- newsapi-python
|
32 |
+
- transformers
|
33 |
+
- streamlit-extras
|
34 |
+
- pandas
|
35 |
+
|
36 |
+
## Usage
|
37 |
+
1. Obtain a NewsAPI key from [newsapi.org](https://newsapi.org/).
|
38 |
+
2. Replace the `api_key` in `app.py` with your NewsAPI key.
|
39 |
+
3. Run the Streamlit app:
|
40 |
+
```bash
|
41 |
+
streamlit run app.py
|
42 |
+
```
|
43 |
+
4. Open the app in your browser, select a sector and time frame, and click "Analyze News" to view results.
|
44 |
+
|
45 |
+
## Files
|
46 |
+
- `app.py`: Main application script with Streamlit code, news fetching, and sentiment analysis.
|
47 |
+
- `requirements.txt`: List of Python dependencies.
|
48 |
+
- `README.md`: Project documentation (this file).
|
49 |
+
|
50 |
+
## Notes
|
51 |
+
- The app uses the `t5-small` model for summarization and `distilbert-base-uncased-finetuned-sst-2-english` for sentiment analysis.
|
52 |
+
- News articles are filtered based on a weighted keyword system to ensure relevance.
|
53 |
+
- Sentiment scores are calculated as (Positive - Negative) / Total Articles.
|
54 |
+
- **Disclaimer**: Insights are for informational purposes only and not financial advice.
|
55 |
+
|
56 |
+
## License
|
57 |
+
MIT License. See [LICENSE](LICENSE) for details.
|
app.py
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from newsapi import NewsApiClient
|
3 |
+
from transformers import pipeline
|
4 |
+
from streamlit_extras.colored_header import colored_header
|
5 |
+
from datetime import datetime, timedelta
|
6 |
+
import pandas as pd
|
7 |
+
import uuid
|
8 |
+
import os
|
9 |
+
|
10 |
+
# NIFTY 50 companies with tickers and sectors
|
11 |
+
nifty_50_data = {
|
12 |
+
"Adani Enterprises": {"ticker": "ADANIENT.NS", "sector": "Industrials"},
|
13 |
+
"Adani Ports": {"ticker": "ADANIPORTS.NS", "sector": "Industrials"},
|
14 |
+
"Apollo Hospitals": {"ticker": "APOLLOHOSP.NS", "sector": "Healthcare"},
|
15 |
+
"Asian Paints": {"ticker": "ASIANPAINT.NS", "sector": "Consumer Discretionary"},
|
16 |
+
"Axis Bank": {"ticker": "AXISBANK.NS", "sector": "Financials"},
|
17 |
+
"Bajaj Auto": {"ticker": "BAJAJ-AUTO.NS", "sector": "Consumer Discretionary"},
|
18 |
+
"Bajaj Finserv": {"ticker": "BAJAJFINSV.NS", "sector": "Financials"},
|
19 |
+
"Bajaj Finance": {"ticker": "BAJFINANCE.NS", "sector": "Financials"},
|
20 |
+
"Bharti Airtel": {"ticker": "BHARTIARTL.NS", "sector": "Communication Services"},
|
21 |
+
"BPCL": {"ticker": "BPCL.NS", "sector": "Energy"},
|
22 |
+
"Britannia": {"ticker": "BRITANNIA.NS", "sector": "Consumer Staples"},
|
23 |
+
"Cipla": {"ticker": "CIPLA.NS", "sector": "Healthcare"},
|
24 |
+
"Coal India": {"ticker": "COALINDIA.NS", "sector": "Energy"},
|
25 |
+
"Divis Labs": {"ticker": "DIVISLAB.NS", "sector": "Healthcare"},
|
26 |
+
"Dr. Reddy's Labs": {"ticker": "DRREDDY.NS", "sector": "Healthcare"},
|
27 |
+
"Eicher Motors": {"ticker": "EICHERMOT.NS", "sector": "Consumer Discretionary"},
|
28 |
+
"Grasim Industries": {"ticker": "GRASIM.NS", "sector": "Materials"},
|
29 |
+
"HCL Technologies": {"ticker": "HCLTECH.NS", "sector": "Information Technology"},
|
30 |
+
"HDFC Bank": {"ticker": "HDFCBANK.NS", "sector": "Financials"},
|
31 |
+
"HDFC Life": {"ticker": "HDFCLIFE.NS", "sector": "Financials"},
|
32 |
+
"Hero MotoCorp": {"ticker": "HEROMOTOCO.NS", "sector": "Consumer Discretionary"},
|
33 |
+
"Hindalco": {"ticker": "HINDALCO.NS", "sector": "Materials"},
|
34 |
+
"HUL": {"ticker": "HINDUNILVR.NS", "sector": "Consumer Staples"},
|
35 |
+
"ICICI Bank": {"ticker": "ICICIBANK.NS", "sector": "Financials"},
|
36 |
+
"IndusInd Bank": {"ticker": "INDUSINDBK.NS", "sector": "Financials"},
|
37 |
+
"Infosys": {"ticker": "INFY.NS", "sector": "Information Technology"},
|
38 |
+
"ITC": {"ticker": "ITC.NS", "sector": "Consumer Staples"},
|
39 |
+
"JSW Steel": {"ticker": "JSWSTEEL.NS", "sector": "Materials"},
|
40 |
+
"Kotak Mahindra Bank": {"ticker": "KOTAKBANK.NS", "sector": "Financials"},
|
41 |
+
"L&T": {"ticker": "LT.NS", "sector": "Industrials"},
|
42 |
+
"L&T Technology Services": {"ticker": "LTIM.NS", "sector": "Information Technology"},
|
43 |
+
"M&M": {"ticker": "M&M.NS", "sector": "Consumer Discretionary"},
|
44 |
+
"Maruti Suzuki": {"ticker": "MARUTI.NS", "sector": "Consumer Discretionary"},
|
45 |
+
"Nestle India": {"ticker": "NESTLEIND.NS", "sector": "Consumer Staples"},
|
46 |
+
"NTPC": {"ticker": "NTPC.NS", "sector": "Utilities"},
|
47 |
+
"ONGC": {"ticker": "ONGC.NS", "sector": "Energy"},
|
48 |
+
"Power Grid": {"ticker": "POWERGRID.NS", "sector": "Utilities"},
|
49 |
+
"Reliance": {"ticker": "RELIANCE.NS", "sector": "Energy"},
|
50 |
+
"SBI Life": {"ticker": "SBILIFE.NS", "sector": "Financials"},
|
51 |
+
"SBI": {"ticker": "SBIN.NS", "sector": "Financials"},
|
52 |
+
"Shriram Finance": {"ticker": "SHRIRAMFIN.NS", "sector": "Financials"},
|
53 |
+
"Sun Pharma": {"ticker": "SUNPHARMA.NS", "sector": "Healthcare"},
|
54 |
+
"Tata Consumer Products": {"ticker": "TATACONSUM.NS", "sector": "Consumer Staples"},
|
55 |
+
"Tata Motors": {"ticker": "TATAMOTORS.NS", "sector": "Consumer Discretionary"},
|
56 |
+
"Tata Steel": {"ticker": "TATASTEEL.NS", "sector": "Materials"},
|
57 |
+
"TCS": {"ticker": "TCS.NS", "sector": "Information Technology"},
|
58 |
+
"Tech Mahindra": {"ticker": "TECHM.NS", "sector": "Information Technology"},
|
59 |
+
"Titan": {"ticker": "TITAN.NS", "sector": "Consumer Discretionary"},
|
60 |
+
"UltraTech Cement": {"ticker": "ULTRACEMCO.NS", "sector": "Materials"},
|
61 |
+
"Wipro": {"ticker": "WIPRO.NS", "sector": "Information Technology"},
|
62 |
+
}
|
63 |
+
|
64 |
+
# Streamlit app setup
|
65 |
+
st.set_page_config(page_title="NIFTY 50 News Analysis", layout="wide")
|
66 |
+
|
67 |
+
# Custom CSS
|
68 |
+
st.markdown("""
|
69 |
+
<style>
|
70 |
+
.stApp {
|
71 |
+
background: linear-gradient(to bottom right, #f0f4f8, #e0e7ff);
|
72 |
+
}
|
73 |
+
.sidebar .sidebar-content {
|
74 |
+
background: linear-gradient(to bottom, #4b5e7e, #7e8aa2);
|
75 |
+
color: white;
|
76 |
+
}
|
77 |
+
.stTable { border: 1px solid #ddd; border-radius: 5px; background: #fff; }
|
78 |
+
.news-container { border: 1px solid #e0e7ff; border-radius: 5px; padding: 10px; margin-bottom: 10px; }
|
79 |
+
</style>
|
80 |
+
""", unsafe_allow_html=True)
|
81 |
+
|
82 |
+
# Sidebar controls
|
83 |
+
with st.sidebar:
|
84 |
+
st.title("NIFTY 50 News Analysis")
|
85 |
+
st.info("Analyze news sentiment for companies by sector over different time frames.")
|
86 |
+
sectors = sorted(set(data['sector'] for data in nifty_50_data.values()))
|
87 |
+
selected_sector = st.selectbox("Select a Sector", sectors)
|
88 |
+
selected_period = st.selectbox("Select Time Frame", ["1D", "5D", "1M", "6M", "YTD", "1Y", "5Y"], index=2)
|
89 |
+
button = st.button("Analyze News")
|
90 |
+
|
91 |
+
# News-related setup
|
92 |
+
newsapi = NewsApiClient(api_key=os.getenv("NEWSAPI_KEY"))
|
93 |
+
summarizer = pipeline("summarization", model="t5-small")
|
94 |
+
classifier = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
|
95 |
+
|
96 |
+
# Keyword weights
|
97 |
+
keyword_weights = {
|
98 |
+
"revenue": 3, "profit": 3, "loss": 3, "earnings": 3, "EBITDA": 3, "quarterly results": 3, "annual report": 3,
|
99 |
+
"share price": 3, "market cap": 3, "dividend": 3, "buyback": 3, "stock split": 3, "bonus issue": 3,
|
100 |
+
"downgrade": 3, "upgrade": 3, "bullish": 3, "bearish": 3, "rating change": 3,
|
101 |
+
"acquisition": 2, "merger": 2, "takeover": 2, "buyout": 2, "new plant": 2, "factory": 2, "expansion": 2,
|
102 |
+
"investment": 2, "launch": 2, "R&D": 2, "deal": 2, "agreement": 2, "MoU": 2, "partnership": 2, "collaboration": 2,
|
103 |
+
"SEBI": 1.5, "fine": 1.5, "violation": 1.5, "compliance": 1.5, "FIR": 1.5, "probe": 1.5, "subsidy": 1.5,
|
104 |
+
"tax": 1.5, "regulation": 1.5, "policy change": 1.5, "license": 1.5, "CEO": 1.5, "CFO": 1.5, "resigns": 1.5,
|
105 |
+
"appointed": 1.5, "stepping down": 1.5, "fraud": 1.5, "scandal": 1.5, "mismanagement": 1.5, "whistleblower": 1.5,
|
106 |
+
"inflation": 1, "GDP": 1, "interest rate": 1, "RBI policy": 1, "sanctions": 1, "trade war": 1, "conflict": 1,
|
107 |
+
"export/import": 1, "recall": 1, "defect": 1, "complaint": 1, "customer issue": 1, "hack": 1, "breach": 1,
|
108 |
+
"cyberattack": 1, "data leak": 1
|
109 |
+
}
|
110 |
+
|
111 |
+
# Function to calculate time range based on selected period
|
112 |
+
def get_date_range(period):
|
113 |
+
end_date = datetime.now()
|
114 |
+
if period == "1D":
|
115 |
+
start_date = end_date - timedelta(days=1)
|
116 |
+
elif period == "5D":
|
117 |
+
start_date = end_date - timedelta(days=5)
|
118 |
+
elif period == "1M":
|
119 |
+
start_date = end_date - timedelta(days=30)
|
120 |
+
elif period == "6M":
|
121 |
+
start_date = end_date - timedelta(days=180)
|
122 |
+
elif period == "YTD":
|
123 |
+
start_date = datetime(end_date.year, 1, 1)
|
124 |
+
elif period == "1Y":
|
125 |
+
start_date = end_date - timedelta(days=365)
|
126 |
+
else: # 5Y
|
127 |
+
start_date = end_date - timedelta(days=365 * 5)
|
128 |
+
return start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d')
|
129 |
+
|
130 |
+
@st.cache_data
|
131 |
+
def fetch_news(company_name, from_date, to_date, page_size=10):
|
132 |
+
try:
|
133 |
+
articles = newsapi.get_everything(
|
134 |
+
q=company_name,
|
135 |
+
from_param=from_date,
|
136 |
+
to=to_date,
|
137 |
+
language="en",
|
138 |
+
sort_by="publishedAt",
|
139 |
+
page_size=page_size
|
140 |
+
)["articles"]
|
141 |
+
relevant_articles = []
|
142 |
+
for article in articles:
|
143 |
+
title = (article.get("title", "") or "").lower()
|
144 |
+
desc = (article.get("description", "") or "").lower()
|
145 |
+
if any(keyword in title or keyword in desc for keyword in keyword_weights.keys()):
|
146 |
+
article["relevance_weight"] = sum(keyword_weights.get(keyword, 0) for keyword in keyword_weights if keyword in title or keyword in desc)
|
147 |
+
relevant_articles.append(article)
|
148 |
+
return sorted(relevant_articles, key=lambda x: x["relevance_weight"], reverse=True)[:10]
|
149 |
+
except Exception as e:
|
150 |
+
st.warning(f"Error fetching news for {company_name}: {str(e)}")
|
151 |
+
return []
|
152 |
+
|
153 |
+
@st.cache_data
|
154 |
+
def summarize_and_classify(news_articles):
|
155 |
+
sentiment_counts = {"Positive": 0, "Negative": 0, "Neutral": 0}
|
156 |
+
summaries = []
|
157 |
+
key_themes = {}
|
158 |
+
for article in news_articles:
|
159 |
+
content = article.get("content", "") or article.get("description", "") or article.get("title", "")
|
160 |
+
if not content:
|
161 |
+
continue
|
162 |
+
summary = summarizer(content[:2048], max_length=100, min_length=30, do_sample=False)[0]["summary_text"] if len(content) > 100 else content
|
163 |
+
sentences = summary.split(". ")
|
164 |
+
key_insight = max(sentences, key=lambda s: sum(keyword_weights.get(k, 0) for k in keyword_weights if k in s.lower()), default=summary)
|
165 |
+
sentiment_result = classifier(summary)[0]
|
166 |
+
sentiment_label = sentiment_result["label"]
|
167 |
+
sentiment_score = sentiment_result["score"]
|
168 |
+
if sentiment_label == "POSITIVE" and sentiment_score > 0.7:
|
169 |
+
sentiment_counts["Positive"] += 1
|
170 |
+
sentiment_display = "Positive"
|
171 |
+
elif sentiment_label == "NEGATIVE" and sentiment_score > 0.7:
|
172 |
+
sentiment_counts["Negative"] += 1
|
173 |
+
sentiment_display = "Negative"
|
174 |
+
else:
|
175 |
+
sentiment_counts["Neutral"] += 1
|
176 |
+
sentiment_display = "Neutral"
|
177 |
+
title = (article.get("title", "") or "").lower()
|
178 |
+
desc = (article.get("description", "") or "").lower()
|
179 |
+
for keyword in keyword_weights:
|
180 |
+
if keyword in title or keyword in desc:
|
181 |
+
key_themes[keyword] = key_themes.get(keyword, 0) + 1
|
182 |
+
summaries.append({
|
183 |
+
"title": article.get("title", "No title"),
|
184 |
+
"summary": summary,
|
185 |
+
"key_insight": key_insight,
|
186 |
+
"sentiment": sentiment_display,
|
187 |
+
"confidence": sentiment_score,
|
188 |
+
"url": article.get("url", "#"),
|
189 |
+
"published_at": article.get("publishedAt", "No date")
|
190 |
+
})
|
191 |
+
top_themes = sorted(key_themes.items(), key=lambda x: x[1], reverse=True)[:3]
|
192 |
+
return sorted(summaries, key=lambda x: x["confidence"], reverse=True)[:5], sentiment_counts, top_themes
|
193 |
+
|
194 |
+
def display_news_articles(news_articles, company_name, selected_period):
|
195 |
+
colored_header(f"Summarized News for {company_name} ({selected_period})",
|
196 |
+
description=f"Key Updates from the Selected Period",
|
197 |
+
color_name="green-70")
|
198 |
+
for news in news_articles:
|
199 |
+
with st.container():
|
200 |
+
st.markdown('<div class="news-container">', unsafe_allow_html=True)
|
201 |
+
col1, col2 = st.columns([3, 1])
|
202 |
+
with col1:
|
203 |
+
st.subheader(news['title'])
|
204 |
+
st.write(f"**Summary**: {news['summary']}")
|
205 |
+
st.write(f"**Key Insight**: {news['key_insight']}")
|
206 |
+
st.markdown(f"[Read More]({news['url']})")
|
207 |
+
with col2:
|
208 |
+
if news['sentiment'] == "Positive":
|
209 |
+
st.markdown(f"<span style='color: green'>π’ Positive ({news['confidence']*100:.1f}%)</span>", unsafe_allow_html=True)
|
210 |
+
elif news['sentiment'] == "Negative":
|
211 |
+
st.markdown(f"<span style='color: red'>π΄ Negative ({news['confidence']*100:.1f}%)</span>", unsafe_allow_html=True)
|
212 |
+
else:
|
213 |
+
st.markdown(f"<span style='color: gray'>βͺ Neutral ({news['confidence']*100:.1f}%)</span>", unsafe_allow_html=True)
|
214 |
+
st.write(f"**Published**: {news['published_at']}")
|
215 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
216 |
+
|
217 |
+
# Main app layout
|
218 |
+
st.title("π° NIFTY 50 Sector News Analysis")
|
219 |
+
st.markdown("Analyze news sentiment for companies in a selected sector to guide investment decisions.", unsafe_allow_html=True)
|
220 |
+
|
221 |
+
if button:
|
222 |
+
with st.spinner("Fetching and analyzing news..."):
|
223 |
+
# Get date range
|
224 |
+
from_date, to_date = get_date_range(selected_period)
|
225 |
+
|
226 |
+
# Filter companies by selected sector
|
227 |
+
companies_in_sector = {name: data for name, data in nifty_50_data.items()
|
228 |
+
if data['sector'] == selected_sector}
|
229 |
+
|
230 |
+
# Fetch and analyze news
|
231 |
+
sentiment_data = []
|
232 |
+
all_news = {}
|
233 |
+
sector_sentiment_counts = {"Positive": 0, "Negative": 0, "Neutral": 0}
|
234 |
+
max_articles = 0
|
235 |
+
|
236 |
+
for company_name in companies_in_sector.keys():
|
237 |
+
news_articles = fetch_news(company_name, from_date, to_date)
|
238 |
+
if news_articles:
|
239 |
+
summarized_news, sentiment_counts, top_themes = summarize_and_classify(news_articles)
|
240 |
+
total_articles = sum(sentiment_counts.values())
|
241 |
+
max_articles = max(max_articles, total_articles)
|
242 |
+
sentiment_score = (sentiment_counts["Positive"] - sentiment_counts["Negative"]) / total_articles if total_articles > 0 else 0
|
243 |
+
dominant_sentiment = max(sentiment_counts, key=sentiment_counts.get)
|
244 |
+
sentiment_data.append({
|
245 |
+
"Company": company_name,
|
246 |
+
"Positive": sentiment_counts["Positive"],
|
247 |
+
"Negative": sentiment_counts["Negative"],
|
248 |
+
"Neutral": sentiment_counts["Neutral"],
|
249 |
+
"Total": total_articles,
|
250 |
+
"Sentiment Score": sentiment_score,
|
251 |
+
"Dominant Sentiment": dominant_sentiment,
|
252 |
+
"Top Themes": [theme[0] for theme in top_themes]
|
253 |
+
})
|
254 |
+
all_news[company_name] = summarized_news
|
255 |
+
for sentiment, count in sentiment_counts.items():
|
256 |
+
sector_sentiment_counts[sentiment] += count
|
257 |
+
|
258 |
+
# Display results
|
259 |
+
if sentiment_data:
|
260 |
+
colored_header(f"Sentiment Analysis for {selected_sector} Sector ({selected_period})",
|
261 |
+
description=f"News from {from_date} to {to_date}",
|
262 |
+
color_name="blue-70")
|
263 |
+
|
264 |
+
# Single sentiment table
|
265 |
+
sentiment_df = pd.DataFrame(sentiment_data)[["Company", "Positive", "Negative", "Neutral", "Total", "Sentiment Score"]]
|
266 |
+
sentiment_df = sentiment_df.sort_values("Sentiment Score", ascending=False)
|
267 |
+
st.subheader("Company Sentiment Overview")
|
268 |
+
st.table(sentiment_df)
|
269 |
+
|
270 |
+
# Decision Guidance
|
271 |
+
colored_header("Decision Guidance", description="Investment Insights Based on News Sentiment", color_name="violet-70")
|
272 |
+
st.markdown("**Note**: These insights are based on news sentiment analysis and are not financial advice. Consult a financial advisor.")
|
273 |
+
sector_total = sum(sector_sentiment_counts.values())
|
274 |
+
sector_positive_pct = (sector_sentiment_counts["Positive"] / sector_total * 100) if sector_total > 0 else 0
|
275 |
+
sector_negative_pct = (sector_sentiment_counts["Negative"] / sector_total * 100) if sector_total > 0 else 0
|
276 |
+
sector_neutral_pct = (sector_sentiment_counts["Neutral"] / sector_total * 100) if sector_total > 0 else 0
|
277 |
+
sector_sentiment = "Positive" if sector_positive_pct > 50 else "Negative" if sector_negative_pct > 50 else "Neutral"
|
278 |
+
st.markdown(f"**Sector Sentiment**: {sector_sentiment} ({sector_positive_pct:.1f}% Positive, {sector_negative_pct:.1f}% Negative, {sector_neutral_pct:.1f}% Neutral)")
|
279 |
+
st.markdown(f"- **Investment Outlook**: {'Favorable' if sector_positive_pct > 50 else 'Cautious' if sector_negative_pct > 50 else 'Neutral'} for selective investments in the {selected_sector} sector.")
|
280 |
+
|
281 |
+
st.markdown("**Company Insights**:")
|
282 |
+
for company in sentiment_data:
|
283 |
+
confidence = "High" if company["Total"] / max_articles > 0.7 else "Medium" if company["Total"] / max_articles > 0.3 else "Low"
|
284 |
+
recommendation = "Consider buying" if company["Sentiment Score"] > 0.3 else "Avoid" if company["Sentiment Score"] < -0.3 else "Monitor"
|
285 |
+
themes_str = ", ".join(company["Top Themes"]) if company["Top Themes"] else "none"
|
286 |
+
st.markdown(f"- **{company['Company']}**: Score: {company['Sentiment Score']:.2f} ({company['Dominant Sentiment']}, driven by {themes_str}), {company['Total']} articles (Confidence: {confidence}). {recommendation}.")
|
287 |
+
|
288 |
+
# Detailed news for each company
|
289 |
+
for company_name in sentiment_df["Company"]:
|
290 |
+
if company_name in all_news and all_news[company_name]:
|
291 |
+
display_news_articles(all_news[company_name], company_name, selected_period)
|
292 |
+
else:
|
293 |
+
st.warning(f"No relevant news found for {selected_sector} sector in the selected period.")
|
nifty-news-analysis
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
Subproject commit 0bad46e406c22516da78dc994b76c60a0693d25c
|
|
|
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.38.0
|
2 |
+
newsapi-python==0.2.7
|
3 |
+
transformers==4.44.2
|
4 |
+
streamlit-extras==0.4.7
|
5 |
+
pandas==2.2.3
|
6 |
+
torch
|