Dmitry Beresnev commited on
Commit
67cf156
Β·
1 Parent(s): 233b29a

add news and tg bot

Browse files
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ TELEGRAM_TOKEN=7997938618:AAF_DJgEToWzCyk7ZvTQ2IHDAv0lJVjWKA8
2
+ FINNHUB_API_TOKEN=d26hq7pr01qvraiqia50d26hq7pr01qvraiqia5g
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .env
2
+ .venv/
3
+ uv.lock
4
+ src/your_project_name.egg-info/
pyproject.toml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "news_sentoment_analyzer"
3
+ version = "0.1.0"
4
+ description = "new sentiment analyzer using lexicon based and machine learning techniques"
5
+ authors = [
6
+ { name = "Your Name", email = "[email protected]" }
7
+ ]
8
+ dependencies = [
9
+ "pandas>=1.5.0",
10
+ "streamlit>=1.28.0",
11
+ "streamlit-option-menu>=0.3.6",
12
+ "transformers>=4.21.0",
13
+ "torch>=1.13.0",
14
+ "torchvision>=0.14.0",
15
+ "torchaudio>=0.13.0",
16
+ "tokenizers>=0.13.0",
17
+ "huggingface-hub>=0.10.0",
18
+ "accelerate>=0.20.0",
19
+ "safetensors>=0.3.0",
20
+ "textblob>=0.17.1",
21
+ "nltk>=3.7",
22
+ "scikit-learn>=1.1.0",
23
+ "plotly>=5.10.0",
24
+ "matplotlib>=3.5.0",
25
+ "seaborn>=0.11.0",
26
+ "numpy>=1.21.0",
27
+ "openpyxl>=3.0.0",
28
+ "xlsxwriter>=3.0.0",
29
+ "yfinance>=0.1.87",
30
+ "pandas-datareader>=0.10.0",
31
+ "alpha-vantage>=2.3.1",
32
+ "finnhub-python>=2.4.24",
33
+ "requests>=2.28.0",
34
+ "beautifulsoup4>=4.11.0",
35
+ "selenium>=4.5.0",
36
+ "feedparser>=6.0.10",
37
+ "newspaper3k>=0.2.8",
38
+ "python-dateutil>=2.8.2",
39
+ "pytz>=2022.1",
40
+ "python-docx>=0.8.11",
41
+ "PyPDF2>=2.12.0",
42
+ "diskcache>=5.4.0",
43
+ "joblib>=1.2.0",
44
+ "python-dotenv>=0.20.0",
45
+ "pyyaml>=6.0",
46
+ "loguru>=0.6.0",
47
+ "pytest>=7.1.0",
48
+ "sqlalchemy>=1.4.0",
49
+ "psycopg2-binary>=2.9.0",
50
+ "tqdm>=4.64.0",
51
+ "click>=8.0.0",
52
+ "typing-extensions>=4.3.0",
53
+ "cryptography>=37.0.0",
54
+ "email-validator>=1.2.0",
55
+ "ratelimit>=2.2.1",
56
+ "backoff>=2.2.1",
57
+ "Pillow>=9.0.0",
58
+ "scipy>=1.9.0",
59
+ "statsmodels>=0.13.0",
60
+ "ta>=0.10.2",
61
+ "black>=22.0.0",
62
+ "flake8>=5.0.0",
63
+ "isort>=5.10.0",
64
+ "python-telegram-bot>=20.0"
65
+ ]
66
+
67
+ [build-system]
68
+ requires = ["setuptools>=61.0", "wheel"]
69
+ build-backend = "setuptools.build_meta"
requirements.txt CHANGED
@@ -108,4 +108,6 @@ isort>=5.10.0
108
  # - redis (not essential for basic functionality)
109
  # - sentry-sdk (optional monitoring)
110
  # - configparser (built-in module)
111
- # - bcrypt (not essential for basic functionality)
 
 
 
108
  # - redis (not essential for basic functionality)
109
  # - sentry-sdk (optional monitoring)
110
  # - configparser (built-in module)
111
+ # - bcrypt (not essential for basic functionality)
112
+
113
+ finnhub-python>=2.4.0
src/financial_news_requester.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from typing import Any
4
+
5
+ from dotenv import load_dotenv
6
+ import finnhub
7
+
8
+
9
+ load_dotenv()
10
+
11
+ api_key = os.getenv('FINNHUB_API_TOKEN')
12
+
13
+
14
+ def fetch_comp_financial_news(ticker: str = 'NVDA', date_from = "2025-07-31", date_to = "2025-08-01") -> list[dict[str, Any]] | None:
15
+ """
16
+ Fetch financial news using Finnhub API.
17
+ """
18
+ try:
19
+ finnhub_client = finnhub.Client(api_key=api_key)
20
+ news_feed = finnhub_client.company_news(ticker, _from=date_from, to=date_to)
21
+ logging.info(f'got total amount of news: {news_feed} for ticker: {ticker}')
22
+ except Exception as e:
23
+ logging.info(f"Error fetching financial news: {e}")
24
+ return None
src/lexicon_based_sentiment_analyzer.py ADDED
File without changes
src/streamlit_app.py DELETED
@@ -1,1068 +0,0 @@
1
- '''
2
-
3
- import altair as alt
4
- import numpy as np
5
- import pandas as pd
6
- import streamlit as st
7
-
8
- """
9
- # Welcome to Streamlit!
10
-
11
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
12
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
13
- forums](https://discuss.streamlit.io).
14
-
15
- In the meantime, below is an example of what you can do with just a few lines of code:
16
- """
17
-
18
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
19
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
20
-
21
- indices = np.linspace(0, 1, num_points)
22
- theta = 2 * np.pi * num_turns * indices
23
- radius = indices
24
-
25
- x = radius * np.cos(theta)
26
- y = radius * np.sin(theta)
27
-
28
- df = pd.DataFrame({
29
- "x": x,
30
- "y": y,
31
- "idx": indices,
32
- "rand": np.random.randn(num_points),
33
- })
34
-
35
- st.altair_chart(alt.Chart(df, height=700, width=700)
36
- .mark_point(filled=True)
37
- .encode(
38
- x=alt.X("x", axis=None),
39
- y=alt.Y("y", axis=None),
40
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
41
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
42
- ))
43
-
44
-
45
-
46
-
47
- import streamlit as st
48
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
49
- import torch
50
- import torch.nn.functional as F
51
- import os
52
- import pandas as pd
53
- import plotly.express as px
54
- import plotly.graph_objects as go
55
- from datetime import datetime
56
- import re
57
-
58
- # Page configuration
59
- st.set_page_config(
60
- page_title="FinBERT Sentiment Analyzer",
61
- page_icon="πŸ’°",
62
- layout="wide",
63
- initial_sidebar_state="expanded"
64
- )
65
-
66
- # Custom CSS for better styling
67
- st.markdown("""
68
- <style>
69
- .main-header {
70
- text-align: center;
71
- color: #1f77b4;
72
- margin-bottom: 2rem;
73
- }
74
- .sentiment-card {
75
- padding: 1rem;
76
- border-radius: 10px;
77
- margin: 0.5rem 0;
78
- text-align: center;
79
- }
80
- .negative { background-color: #ffebee; border-left: 5px solid #f44336; }
81
- .neutral { background-color: #f3e5f5; border-left: 5px solid #9c27b0; }
82
- .positive { background-color: #e8f5e8; border-left: 5px solid #4caf50; }
83
- .metric-container {
84
- background-color: #f8f9fa;
85
- padding: 1rem;
86
- border-radius: 10px;
87
- margin: 1rem 0;
88
- }
89
- </style>
90
- """, unsafe_allow_html=True)
91
-
92
- st.markdown('<h1 class="main-header">πŸ’° FinBERT: Financial Sentiment Analysis</h1>', unsafe_allow_html=True)
93
-
94
- # Sidebar
95
- with st.sidebar:
96
- st.header("ℹ️ About")
97
- st.markdown("""
98
- **Model:** `yiyanghkust/finbert-tone`
99
- Trained specifically on financial texts for accurate sentiment analysis of:
100
- - Financial news
101
- - Earnings reports
102
- - Market analysis
103
- - Investment research
104
- """)
105
-
106
- st.header("βš™οΈ Settings")
107
- confidence_threshold = st.slider("Confidence Threshold", 0.0, 1.0, 0.5, help="Minimum confidence for sentiment classification")
108
- show_probabilities = st.checkbox("Show All Probabilities", value=True)
109
- batch_analysis = st.checkbox("Enable Batch Analysis", help="Analyze multiple texts at once")
110
-
111
- @st.cache_resource(show_spinner=False)
112
- def load_model():
113
- """Load FinBERT model and tokenizer with error handling"""
114
- try:
115
- cache_dir = "/tmp/huggingface"
116
- os.makedirs(cache_dir, exist_ok=True)
117
-
118
- with st.spinner("Loading FinBERT model... This may take a moment."):
119
- tokenizer = AutoTokenizer.from_pretrained(
120
- "yiyanghkust/finbert-tone",
121
- cache_dir=cache_dir
122
- )
123
- model = AutoModelForSequenceClassification.from_pretrained(
124
- "yiyanghkust/finbert-tone",
125
- cache_dir=cache_dir
126
- )
127
- return tokenizer, model, None
128
- except Exception as e:
129
- return None, None, str(e)
130
-
131
- def analyze_sentiment(text, tokenizer, model):
132
- """Analyze sentiment with error handling and additional metrics"""
133
- try:
134
- # Preprocess text
135
- text = re.sub(r'\s+', ' ', text.strip())
136
-
137
- inputs = tokenizer(
138
- text,
139
- return_tensors="pt",
140
- truncation=True,
141
- padding=True,
142
- max_length=512
143
- )
144
-
145
- with torch.no_grad():
146
- outputs = model(**inputs)
147
- probs = F.softmax(outputs.logits, dim=1).squeeze()
148
-
149
- labels = ["Negative", "Neutral", "Positive"]
150
- sentiment_scores = {label: prob.item() for label, prob in zip(labels, probs)}
151
-
152
- # Determine primary sentiment
153
- max_prob = max(sentiment_scores.values())
154
- primary_sentiment = max(sentiment_scores, key=sentiment_scores.get)
155
-
156
- return sentiment_scores, primary_sentiment, max_prob, None
157
- except Exception as e:
158
- return None, None, None, str(e)
159
-
160
- def create_sentiment_chart(sentiment_scores):
161
- """Create an interactive sentiment visualization"""
162
- labels = list(sentiment_scores.keys())
163
- values = list(sentiment_scores.values())
164
- colors = ['#f44336', '#9c27b0', '#4caf50']
165
-
166
- fig = go.Figure(data=[
167
- go.Bar(
168
- x=labels,
169
- y=values,
170
- marker_color=colors,
171
- text=[f'{v:.3f}' for v in values],
172
- textposition='auto',
173
- )
174
- ])
175
-
176
- fig.update_layout(
177
- title="Sentiment Analysis Results",
178
- xaxis_title="Sentiment",
179
- yaxis_title="Confidence Score",
180
- yaxis=dict(range=[0, 1]),
181
- height=400,
182
- showlegend=False
183
- )
184
-
185
- return fig
186
-
187
- # Load model
188
- tokenizer, model, error = load_model()
189
-
190
- if error:
191
- st.error(f"Failed to load model: {error}")
192
- st.stop()
193
-
194
- if tokenizer and model:
195
- st.success("βœ… FinBERT model loaded successfully!")
196
-
197
- # Main analysis interface
198
- if not batch_analysis:
199
- st.header("πŸ“ Single Text Analysis")
200
- text = st.text_area(
201
- "Enter financial news, report, or analysis:",
202
- height=150,
203
- placeholder="Example: The company reported strong quarterly earnings with revenue growth of 15% year-over-year..."
204
- )
205
-
206
- col1, col2, col3 = st.columns([1, 1, 2])
207
- with col1:
208
- analyze_button = st.button("πŸ” Analyze Sentiment", type="primary")
209
- with col2:
210
- clear_button = st.button("πŸ—‘οΈ Clear")
211
-
212
- if clear_button:
213
- st.rerun()
214
-
215
- if analyze_button and text.strip():
216
- with st.spinner("Analyzing sentiment..."):
217
- sentiment_scores, primary_sentiment, confidence, error = analyze_sentiment(text, tokenizer, model)
218
-
219
- if error:
220
- st.error(f"Analysis failed: {error}")
221
- else:
222
- # Results section
223
- st.header("πŸ“Š Analysis Results")
224
-
225
- # Primary sentiment with confidence
226
- col1, col2, col3 = st.columns(3)
227
-
228
- sentiment_emojis = {"Negative": "πŸ“‰", "Neutral": "😐", "Positive": "πŸ“ˆ"}
229
- sentiment_colors = {"Negative": "red", "Neutral": "gray", "Positive": "green"}
230
-
231
- with col1:
232
- st.metric(
233
- "Primary Sentiment",
234
- f"{sentiment_emojis[primary_sentiment]} {primary_sentiment}",
235
- delta=f"{confidence:.1%} confidence"
236
- )
237
-
238
- with col2:
239
- st.metric(
240
- "Text Length",
241
- f"{len(text)} characters",
242
- delta=f"{len(text.split())} words"
243
- )
244
-
245
- with col3:
246
- reliability = "High" if confidence > 0.7 else "Medium" if confidence > 0.5 else "Low"
247
- st.metric("Reliability", reliability)
248
-
249
- # Detailed probabilities
250
- if show_probabilities:
251
- st.subheader("Detailed Sentiment Scores")
252
-
253
- for sentiment, score in sentiment_scores.items():
254
- emoji = sentiment_emojis[sentiment]
255
- color = "negative" if sentiment == "Negative" else "neutral" if sentiment == "Neutral" else "positive"
256
-
257
- st.markdown(f"""
258
- <div class="sentiment-card {color}">
259
- <h4>{emoji} {sentiment}</h4>
260
- <h2>{score:.3f}</h2>
261
- <div style="width: 100%; background-color: #ddd; border-radius: 25px;">
262
- <div style="width: {score*100}%; height: 10px; background-color: {sentiment_colors[sentiment]}; border-radius: 25px;"></div>
263
- </div>
264
- </div>
265
- """, unsafe_allow_html=True)
266
-
267
- # Visualization
268
- st.subheader("πŸ“ˆ Sentiment Visualization")
269
- fig = create_sentiment_chart(sentiment_scores)
270
- st.plotly_chart(fig, use_container_width=True)
271
-
272
- else:
273
- # Batch analysis mode
274
- st.header("πŸ“Š Batch Analysis")
275
-
276
- # Option to upload file or enter multiple texts
277
- analysis_method = st.radio(
278
- "Choose analysis method:",
279
- ["Enter multiple texts", "Upload CSV file"]
280
- )
281
-
282
- if analysis_method == "Enter multiple texts":
283
- texts_input = st.text_area(
284
- "Enter multiple texts (one per line):",
285
- height=200,
286
- placeholder="Text 1: Company reports strong earnings...\nText 2: Market volatility increases...\nText 3: New regulations impact sector..."
287
- )
288
-
289
- if st.button("πŸ” Analyze All Texts") and texts_input.strip():
290
- texts = [text.strip() for text in texts_input.split('\n') if text.strip()]
291
-
292
- if texts:
293
- results = []
294
- progress_bar = st.progress(0)
295
-
296
- for i, text in enumerate(texts):
297
- sentiment_scores, primary_sentiment, confidence, error = analyze_sentiment(text, tokenizer, model)
298
-
299
- if not error:
300
- results.append({
301
- 'Text': text[:100] + '...' if len(text) > 100 else text,
302
- 'Primary Sentiment': primary_sentiment,
303
- 'Confidence': confidence,
304
- 'Negative': sentiment_scores['Negative'],
305
- 'Neutral': sentiment_scores['Neutral'],
306
- 'Positive': sentiment_scores['Positive']
307
- })
308
-
309
- progress_bar.progress((i + 1) / len(texts))
310
-
311
- if results:
312
- df = pd.DataFrame(results)
313
-
314
- # Summary statistics
315
- st.subheader("πŸ“ˆ Batch Analysis Summary")
316
- col1, col2, col3 = st.columns(3)
317
-
318
- with col1:
319
- positive_count = len(df[df['Primary Sentiment'] == 'Positive'])
320
- st.metric("Positive Texts", positive_count, f"{positive_count/len(df)*100:.1f}%")
321
-
322
- with col2:
323
- neutral_count = len(df[df['Primary Sentiment'] == 'Neutral'])
324
- st.metric("Neutral Texts", neutral_count, f"{neutral_count/len(df)*100:.1f}%")
325
-
326
- with col3:
327
- negative_count = len(df[df['Primary Sentiment'] == 'Negative'])
328
- st.metric("Negative Texts", negative_count, f"{negative_count/len(df)*100:.1f}%")
329
-
330
- # Results table
331
- st.subheader("πŸ“‹ Detailed Results")
332
- st.dataframe(df, use_container_width=True)
333
-
334
- # Download results
335
- csv = df.to_csv(index=False)
336
- st.download_button(
337
- "πŸ“₯ Download Results (CSV)",
338
- csv,
339
- f"sentiment_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
340
- "text/csv"
341
- )
342
-
343
- elif analysis_method == "Upload CSV file":
344
- uploaded_file = st.file_uploader(
345
- "Choose a CSV file with a 'text' column",
346
- type=['csv']
347
- )
348
-
349
- if uploaded_file is not None:
350
- try:
351
- df = pd.read_csv(uploaded_file)
352
-
353
- if 'text' not in df.columns:
354
- st.error("CSV file must contain a 'text' column")
355
- else:
356
- st.write(f"Loaded {len(df)} texts from CSV file")
357
- st.dataframe(df.head(), use_container_width=True)
358
-
359
- if st.button("πŸ” Analyze CSV Data"):
360
- results = []
361
- progress_bar = st.progress(0)
362
-
363
- for i, row in df.iterrows():
364
- text = str(row['text'])
365
- sentiment_scores, primary_sentiment, confidence, error = analyze_sentiment(text, tokenizer, model)
366
-
367
- if not error:
368
- result_row = row.to_dict()
369
- result_row.update({
370
- 'Primary Sentiment': primary_sentiment,
371
- 'Confidence': confidence,
372
- 'Negative Score': sentiment_scores['Negative'],
373
- 'Neutral Score': sentiment_scores['Neutral'],
374
- 'Positive Score': sentiment_scores['Positive']
375
- })
376
- results.append(result_row)
377
-
378
- progress_bar.progress((i + 1) / len(df))
379
-
380
- if results:
381
- results_df = pd.DataFrame(results)
382
-
383
- # Display results
384
- st.subheader("πŸ“‹ Analysis Results")
385
- st.dataframe(results_df, use_container_width=True)
386
-
387
- # Download enhanced results
388
- csv = results_df.to_csv(index=False)
389
- st.download_button(
390
- "πŸ“₯ Download Enhanced Results (CSV)",
391
- csv,
392
- f"enhanced_sentiment_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
393
- "text/csv"
394
- )
395
-
396
- except Exception as e:
397
- st.error(f"Error processing CSV file: {str(e)}")
398
-
399
- # Footer
400
- st.markdown("---")
401
- st.markdown("""
402
- <div style='text-align: center; color: #666; margin-top: 2rem;'>
403
- <p>πŸ’‘ <strong>Tip:</strong> For best results, use complete sentences and financial context</p>
404
- <p>Built with Streamlit β€’ Powered by FinBERT</p>
405
- </div>
406
- """, unsafe_allow_html=True)
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
- import streamlit as st
415
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
416
- import torch
417
- import torch.nn.functional as F
418
- import os
419
-
420
- st.set_page_config(page_title="πŸ’° FinBERT: Financial Sentiment Analysis", layout="centered")
421
- st.title("πŸ’° FinBERT: Financial Sentiment Analysis")
422
- st.markdown("МодСль: `yiyanghkust/finbert-tone` β€” ΠΎΠ±ΡƒΡ‡Π΅Π½Π° Π½Π° финансовых тСкстах")
423
-
424
- @st.cache_resource
425
- def load_model():
426
- # Установка кастомного ΠΏΡƒΡ‚ΠΈ ΠΊ ΠΊΡΡˆΡƒ
427
- cache_dir = "/tmp/huggingface"
428
- os.makedirs(cache_dir, exist_ok=True)
429
-
430
- tokenizer = AutoTokenizer.from_pretrained("yiyanghkust/finbert-tone", cache_dir=cache_dir)
431
- model = AutoModelForSequenceClassification.from_pretrained("yiyanghkust/finbert-tone", cache_dir=cache_dir)
432
- return tokenizer, model
433
-
434
- tokenizer, model = load_model()
435
-
436
- text = st.text_area("Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Ρ„ΠΈΠ½Π°Π½ΡΠΎΠ²ΡƒΡŽ Π½ΠΎΠ²ΠΎΡΡ‚ΡŒ ΠΈΠ»ΠΈ ΠΎΡ‚Ρ‡Ρ‘Ρ‚:", height=150)
437
-
438
- if st.button("ΠΠ½Π°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ") and text.strip():
439
- inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
440
- with torch.no_grad():
441
- outputs = model(**inputs)
442
- probs = F.softmax(outputs.logits, dim=1).squeeze()
443
-
444
- labels = ["πŸ“‰ Negative", "😐 Neutral", "πŸ“ˆ Positive"]
445
- for label, prob in zip(labels, probs):
446
- st.write(f"**{label}:** {prob.item():.3f}")
447
-
448
- '''
449
-
450
-
451
- import time
452
- import os
453
- from datetime import datetime, timedelta
454
- import re
455
-
456
- import yfinance as yf
457
- import streamlit as st
458
- from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModelForTokenClassification, pipeline
459
- import torch
460
- import torch.nn.functional as F
461
- import pandas as pd
462
- import plotly.express as px
463
- import plotly.graph_objects as go
464
- from plotly.subplots import make_subplots
465
- import numpy as np
466
- from textblob import TextBlob
467
- import requests
468
- from bs4 import BeautifulSoup
469
-
470
-
471
- # Page configuration
472
- st.set_page_config(
473
- page_title="Financial News Sentiment Analyzer",
474
- page_icon="πŸ“ˆ",
475
- layout="wide",
476
- initial_sidebar_state="expanded"
477
- )
478
-
479
- # Custom CSS for financial theme
480
- st.markdown("""
481
- <style>
482
- .main-header {
483
- text-align: center;
484
- background: linear-gradient(90deg, #1f4e79, #2e7d32);
485
- color: white;
486
- padding: 1rem;
487
- border-radius: 15px;
488
- margin-bottom: 2rem;
489
- }
490
- .metric-card {
491
- background: white;
492
- padding: 1.5rem;
493
- border-radius: 10px;
494
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
495
- border-left: 4px solid #1f4e79;
496
- margin: 1rem 0;
497
- }
498
- .bullish { border-left-color: #4caf50 !important; }
499
- .bearish { border-left-color: #f44336 !important; }
500
- .neutral { border-left-color: #ff9800 !important; }
501
- .market-impact {
502
- padding: 1rem;
503
- border-radius: 8px;
504
- margin: 0.5rem 0;
505
- font-weight: bold;
506
- }
507
- .high-impact { background-color: #ffebee; color: #c62828; }
508
- .medium-impact { background-color: #fff3e0; color: #ef6c00; }
509
- .low-impact { background-color: #e8f5e8; color: #2e7d32; }
510
- .trading-signal {
511
- padding: 1rem;
512
- border-radius: 10px;
513
- text-align: center;
514
- font-size: 1.2rem;
515
- font-weight: bold;
516
- margin: 1rem 0;
517
- }
518
- .buy-signal { background: linear-gradient(135deg, #4caf50, #66bb6a); color: white; }
519
- .sell-signal { background: linear-gradient(135deg, #f44336, #ef5350); color: white; }
520
- .hold-signal { background: linear-gradient(135deg, #ff9800, #ffa726); color: white; }
521
- .risk-indicator {
522
- display: inline-block;
523
- padding: 0.3rem 0.8rem;
524
- border-radius: 20px;
525
- font-size: 0.9rem;
526
- font-weight: bold;
527
- margin: 0.2rem;
528
- }
529
- .risk-low { background-color: #4caf50; color: white; }
530
- .risk-medium { background-color: #ff9800; color: white; }
531
- .risk-high { background-color: #f44336; color: white; }
532
- </style>
533
- """, unsafe_allow_html=True)
534
-
535
- st.markdown('<div class="main-header"><h1>πŸ“ˆ Financial News Sentiment Analysis Platform</h1><p>AI-Powered Market Intelligence & Trading Insights</p></div>', unsafe_allow_html=True)
536
-
537
- # Sidebar configuration
538
- with st.sidebar:
539
- st.header("🎯 Analysis Configuration")
540
-
541
- analysis_type = st.selectbox(
542
- "Analysis Type:",
543
- ["Single News Analysis", "Portfolio Impact Analysis", "Market Sector Analysis", "Real-time News Feed"]
544
- )
545
-
546
- st.header("πŸ“Š Financial Models")
547
- model_choice = st.selectbox(
548
- "Sentiment Model:",
549
- ["FinBERT (Financial)", "RoBERTa (General)", "Custom Ensemble"]
550
- )
551
-
552
- st.header("βš™οΈ Trading Parameters")
553
- risk_tolerance = st.selectbox("Risk Tolerance:", ["Conservative", "Moderate", "Aggressive"])
554
- investment_horizon = st.selectbox("Investment Horizon:", ["Day Trading", "Swing (1-7 days)", "Position (1-3 months)", "Long-term (6+ months)"])
555
- position_size = st.slider("Position Size ($)", 1000, 100000, 10000, 1000)
556
-
557
- st.header("πŸŽ›οΈ Alert Settings")
558
- sentiment_threshold = st.slider("Sentiment Alert Threshold", 0.0, 1.0, 0.7)
559
- enable_notifications = st.checkbox("Enable Trading Alerts")
560
-
561
- @st.cache_resource
562
- def load_financial_models():
563
- """Load multiple financial sentiment models"""
564
- try:
565
- cache_dir = "/tmp/huggingface"
566
- os.makedirs(cache_dir, exist_ok=True)
567
-
568
- # FinBERT for financial sentiment
569
- finbert_tokenizer = AutoTokenizer.from_pretrained("yiyanghkust/finbert-tone", cache_dir=cache_dir)
570
- finbert_model = AutoModelForSequenceClassification.from_pretrained("yiyanghkust/finbert-tone", cache_dir=cache_dir)
571
-
572
- # Financial NER for entity extraction
573
- #ner_pipeline = pipeline("ner", model="elastic/distilbert-base-cased-finetuned-conll03-english", aggregation_strategy="simple", cache_dir=cache_dir)
574
-
575
- # Load Financial NER model and tokenizer explicitly
576
- ner_tokenizer = AutoTokenizer.from_pretrained(
577
- "Jean-Baptiste/roberta-large-ner-english", cache_dir=cache_dir
578
- )
579
- ner_model = AutoModelForTokenClassification.from_pretrained(
580
- "Jean-Baptiste/roberta-large-ner-english", cache_dir=cache_dir
581
- )
582
-
583
- # Then create pipeline using objects
584
- ner_pipeline = pipeline(
585
- "ner",
586
- model=ner_model,
587
- tokenizer=ner_tokenizer,
588
- aggregation_strategy="simple",
589
- )
590
-
591
- return finbert_tokenizer, finbert_model, ner_pipeline, None
592
- except Exception as e:
593
- return None, None, None, str(e)
594
-
595
- def extract_financial_entities(text, ner_pipeline):
596
- """Extract companies, stocks, and financial entities from text"""
597
- try:
598
- entities = ner_pipeline(text)
599
-
600
- # Common financial terms and patterns
601
- financial_patterns = {
602
- 'stocks': r'\b([A-Z]{1,5})\b(?=\s*(?:stock|shares|equity))',
603
- 'currencies': r'\b(USD|EUR|GBP|JPY|CHF|CAD|AUD|CNY)\b',
604
- 'sectors': r'\b(technology|healthcare|finance|energy|utilities|materials|industrials|consumer|real estate)\b',
605
- 'metrics': r'\b(revenue|earnings|profit|loss|margin|growth|decline|volatility)\b'
606
- }
607
-
608
- extracted = {
609
- 'companies': [ent['word'] for ent in entities if ent['entity_group'] == 'ORG'],
610
- 'persons': [ent['word'] for ent in entities if ent['entity_group'] == 'PER'],
611
- 'locations': [ent['word'] for ent in entities if ent['entity_group'] == 'LOC']
612
- }
613
-
614
- # Extract financial patterns
615
- for category, pattern in financial_patterns.items():
616
- matches = re.findall(pattern, text, re.IGNORECASE)
617
- extracted[category] = matches
618
-
619
- return extracted
620
- except:
621
- return {}
622
-
623
- def analyze_financial_sentiment(text, tokenizer, model):
624
- """Comprehensive financial sentiment analysis"""
625
- try:
626
- # Basic sentiment analysis
627
- inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=512)
628
-
629
- with torch.no_grad():
630
- outputs = model(**inputs)
631
- probs = F.softmax(outputs.logits, dim=1).squeeze()
632
-
633
- sentiment_scores = {
634
- 'bearish': probs[0].item(),
635
- 'neutral': probs[1].item(),
636
- 'bullish': probs[2].item()
637
- }
638
-
639
- # Determine primary sentiment
640
- primary_sentiment = max(sentiment_scores, key=sentiment_scores.get)
641
- confidence = max(sentiment_scores.values())
642
-
643
- # Financial impact analysis
644
- impact_keywords = {
645
- 'high_impact': ['earnings', 'revenue', 'acquisition', 'merger', 'bankruptcy', 'lawsuit', 'regulatory', 'FDA approval'],
646
- 'medium_impact': ['guidance', 'outlook', 'partnership', 'contract', 'expansion', 'leadership'],
647
- 'low_impact': ['minor', 'slight', 'maintenance', 'routine', 'administrative']
648
- }
649
-
650
- text_lower = text.lower()
651
- impact_level = 'low'
652
-
653
- for level, keywords in impact_keywords.items():
654
- if any(keyword in text_lower for keyword in keywords):
655
- impact_level = level.replace('_impact', '')
656
- break
657
-
658
- # Market volatility prediction
659
- volatility_indicators = ['volatile', 'uncertain', 'fluctuation', 'swing', 'dramatic', 'sudden']
660
- volatility_score = sum(1 for indicator in volatility_indicators if indicator in text_lower) / len(volatility_indicators)
661
-
662
- # Risk assessment
663
- risk_factors = ['risk', 'concern', 'challenge', 'threat', 'uncertainty', 'decline', 'loss']
664
- risk_score = sum(1 for factor in risk_factors if factor in text_lower) / len(risk_factors)
665
-
666
- return {
667
- 'sentiment_scores': sentiment_scores,
668
- 'primary_sentiment': primary_sentiment,
669
- 'confidence': confidence,
670
- 'market_impact': impact_level,
671
- 'volatility_score': volatility_score,
672
- 'risk_score': risk_score
673
- }
674
-
675
- except Exception as e:
676
- return None
677
-
678
- def generate_trading_signals(analysis_result, entities, risk_tolerance, investment_horizon):
679
- """Generate actionable trading signals based on sentiment analysis"""
680
-
681
- if not analysis_result:
682
- return None
683
-
684
- sentiment = analysis_result['primary_sentiment']
685
- confidence = analysis_result['confidence']
686
- impact = analysis_result['market_impact']
687
- risk_score = analysis_result['risk_score']
688
-
689
- # Base signal determination
690
- if sentiment == 'bullish' and confidence > 0.7:
691
- base_signal = 'BUY'
692
- elif sentiment == 'bearish' and confidence > 0.7:
693
- base_signal = 'SELL'
694
- else:
695
- base_signal = 'HOLD'
696
-
697
- # Adjust based on risk tolerance
698
- risk_multipliers = {
699
- 'Conservative': 0.7,
700
- 'Moderate': 1.0,
701
- 'Aggressive': 1.3
702
- }
703
-
704
- adjusted_confidence = confidence * risk_multipliers[risk_tolerance]
705
-
706
- # Time horizon adjustments
707
- horizon_adjustments = {
708
- 'Day Trading': {'threshold': 0.8, 'hold_bias': 0.1},
709
- 'Swing (1-7 days)': {'threshold': 0.7, 'hold_bias': 0.2},
710
- 'Position (1-3 months)': {'threshold': 0.6, 'hold_bias': 0.3},
711
- 'Long-term (6+ months)': {'threshold': 0.5, 'hold_bias': 0.4}
712
- }
713
-
714
- threshold = horizon_adjustments[investment_horizon]['threshold']
715
-
716
- # Final signal
717
- if adjusted_confidence < threshold:
718
- final_signal = 'HOLD'
719
- else:
720
- final_signal = base_signal
721
-
722
- # Position sizing recommendation
723
- if impact == 'high' and confidence > 0.8:
724
- position_multiplier = 1.2
725
- elif impact == 'low' or confidence < 0.6:
726
- position_multiplier = 0.7
727
- else:
728
- position_multiplier = 1.0
729
-
730
- return {
731
- 'signal': final_signal,
732
- 'confidence': adjusted_confidence,
733
- 'position_multiplier': position_multiplier,
734
- 'risk_level': 'High' if risk_score > 0.6 else 'Medium' if risk_score > 0.3 else 'Low',
735
- 'rationale': f"{sentiment.title()} sentiment ({confidence:.1%}) with {impact} market impact"
736
- }
737
-
738
- def create_sentiment_dashboard(analysis_result, entities, trading_signal):
739
- """Create comprehensive financial dashboard"""
740
-
741
- if not analysis_result:
742
- return None
743
-
744
- # Create subplots
745
- fig = make_subplots(
746
- rows=2, cols=2,
747
- subplot_titles=('Sentiment Distribution', 'Market Impact vs Confidence', 'Risk Assessment', 'Trading Signal'),
748
- specs=[[{"type": "bar"}, {"type": "scatter"}],
749
- [{"type": "indicator"}, {"type": "bar"}]]
750
- )
751
-
752
- # Sentiment distribution
753
- sentiments = list(analysis_result['sentiment_scores'].keys())
754
- scores = list(analysis_result['sentiment_scores'].values())
755
- colors = ['#f44336', '#ff9800', '#4caf50']
756
-
757
- fig.add_trace(
758
- go.Bar(x=sentiments, y=scores, marker_color=colors, showlegend=False),
759
- row=1, col=1
760
- )
761
-
762
- # Market impact vs confidence
763
- impact_mapping = {'low': 1, 'medium': 2, 'high': 3}
764
- fig.add_trace(
765
- go.Scatter(
766
- x=[analysis_result['confidence']],
767
- y=[impact_mapping[analysis_result['market_impact']]],
768
- mode='markers',
769
- marker=dict(size=20, color='red' if trading_signal['signal'] == 'SELL' else 'green' if trading_signal['signal'] == 'BUY' else 'orange'),
770
- showlegend=False
771
- ),
772
- row=1, col=2
773
- )
774
-
775
- # Risk gauge
776
- fig.add_trace(
777
- go.Indicator(
778
- mode="gauge+number",
779
- value=analysis_result['risk_score'] * 100,
780
- domain={'x': [0, 1], 'y': [0, 1]},
781
- title={'text': "Risk Level (%)"},
782
- gauge={
783
- 'axis': {'range': [None, 100]},
784
- 'bar': {'color': "darkblue"},
785
- 'steps': [
786
- {'range': [0, 30], 'color': "lightgreen"},
787
- {'range': [30, 70], 'color': "yellow"},
788
- {'range': [70, 100], 'color': "red"}
789
- ],
790
- 'threshold': {
791
- 'line': {'color': "red", 'width': 4},
792
- 'thickness': 0.75,
793
- 'value': 80
794
- }
795
- }
796
- ),
797
- row=2, col=1
798
- )
799
-
800
- # Trading signal strength
801
- signal_strength = trading_signal['confidence'] * 100
802
- fig.add_trace(
803
- go.Bar(
804
- x=[trading_signal['signal']],
805
- y=[signal_strength],
806
- marker_color='green' if trading_signal['signal'] == 'BUY' else 'red' if trading_signal['signal'] == 'SELL' else 'orange',
807
- showlegend=False
808
- ),
809
- row=2, col=2
810
- )
811
-
812
- fig.update_layout(height=600, title_text="Financial Sentiment Analysis Dashboard")
813
- return fig
814
-
815
- # Load models
816
- tokenizer, model, ner_pipeline, error = load_financial_models()
817
-
818
- if error:
819
- st.error(f"Failed to load models: {error}")
820
- st.stop()
821
-
822
- if tokenizer and model:
823
- st.success("βœ… Financial AI models loaded successfully!")
824
-
825
- if analysis_type == "Single News Analysis":
826
- st.header("πŸ“° Single News Analysis")
827
-
828
- col1, col2 = st.columns([2, 1])
829
-
830
- with col1:
831
- news_text = st.text_area(
832
- "Enter financial news or press release:",
833
- height=200,
834
- placeholder="Example: Apple Inc. reported record quarterly earnings of $123.9 billion, beating analyst expectations by 15%. The company's iPhone sales surged 25% year-over-year, driven by strong demand for the new iPhone 15 series..."
835
- )
836
-
837
- col_a, col_b = st.columns(2)
838
- with col_a:
839
- analyze_btn = st.button("πŸ” Analyze News", type="primary")
840
- with col_b:
841
- if st.button("πŸ“Š Get Sample News"):
842
- sample_news = [
843
- "Tesla reports record Q4 deliveries, exceeding analyst expectations by 12%. Stock surges in after-hours trading.",
844
- "Federal Reserve signals potential rate cuts amid cooling inflation data. Markets rally on dovish commentary.",
845
- "Major tech stocks decline following concerns over AI regulation and increased government oversight.",
846
- ]
847
- st.session_state.sample_news = np.random.choice(sample_news)
848
-
849
- if 'sample_news' in st.session_state:
850
- news_text = st.session_state.sample_news
851
-
852
- with col2:
853
- st.subheader("🎯 Quick Actions")
854
- if st.button("πŸ“ˆ Market Impact Simulator"):
855
- st.info("Feature available in Pro version")
856
- if st.button("πŸ“§ Setup Alert"):
857
- st.info("Alert configured successfully!")
858
- if st.button("πŸ’Ύ Save Analysis"):
859
- st.info("Analysis saved to portfolio")
860
-
861
- if analyze_btn and news_text.strip():
862
- with st.spinner("πŸ€– Analyzing financial sentiment..."):
863
- # Extract entities
864
- entities = extract_financial_entities(news_text, ner_pipeline)
865
-
866
- # Analyze sentiment
867
- analysis_result = analyze_financial_sentiment(news_text, tokenizer, model)
868
-
869
- # Generate trading signals
870
- trading_signal = generate_trading_signals(
871
- analysis_result, entities, risk_tolerance, investment_horizon
872
- )
873
-
874
- if analysis_result and trading_signal:
875
- # Display results
876
- st.header("πŸ“Š Financial Analysis Results")
877
-
878
- # Key metrics row
879
- col1, col2, col3, col4 = st.columns(4)
880
-
881
- with col1:
882
- sentiment_emoji = "πŸ‚" if analysis_result['primary_sentiment'] == 'bullish' else "🐻" if analysis_result['primary_sentiment'] == 'bearish' else "➑️"
883
- st.metric(
884
- "Market Sentiment",
885
- f"{sentiment_emoji} {analysis_result['primary_sentiment'].title()}",
886
- f"{analysis_result['confidence']:.1%} confidence"
887
- )
888
-
889
- with col2:
890
- impact_emoji = "πŸ”΄" if analysis_result['market_impact'] == 'high' else "🟑" if analysis_result['market_impact'] == 'medium' else "🟒"
891
- st.metric(
892
- "Market Impact",
893
- f"{impact_emoji} {analysis_result['market_impact'].title()}",
894
- f"Risk: {trading_signal['risk_level']}"
895
- )
896
-
897
- with col3:
898
- st.metric(
899
- "Volatility Score",
900
- f"{analysis_result['volatility_score']:.1%}",
901
- "Expected price movement"
902
- )
903
-
904
- with col4:
905
- recommended_position = position_size * trading_signal['position_multiplier']
906
- st.metric(
907
- "Position Size",
908
- f"${recommended_position:,.0f}",
909
- f"{(trading_signal['position_multiplier']-1)*100:+.0f}% vs base"
910
- )
911
-
912
- # Trading signal
913
- signal_class = f"{trading_signal['signal'].lower()}-signal"
914
- st.markdown(f"""
915
- <div class="trading-signal {signal_class}">
916
- 🎯 TRADING SIGNAL: {trading_signal['signal']}
917
- <br><small>{trading_signal['rationale']}</small>
918
- </div>
919
- """, unsafe_allow_html=True)
920
-
921
- # Detailed analysis
922
- col1, col2 = st.columns(2)
923
-
924
- with col1:
925
- st.subheader("πŸ“ˆ Sentiment Breakdown")
926
- for sentiment, score in analysis_result['sentiment_scores'].items():
927
- sentiment_class = 'bullish' if sentiment == 'bullish' else 'bearish' if sentiment == 'bearish' else 'neutral'
928
- st.markdown(f"""
929
- <div class="metric-card {sentiment_class}">
930
- <h4>{'πŸ‚' if sentiment == 'bullish' else '🐻' if sentiment == 'bearish' else '➑️'} {sentiment.title()}</h4>
931
- <h2>{score:.3f}</h2>
932
- <div style="width: 100%; background-color: #ddd; border-radius: 25px; height: 10px;">
933
- <div style="width: {score*100}%; height: 10px; background-color: {'#4caf50' if sentiment == 'bullish' else '#f44336' if sentiment == 'bearish' else '#ff9800'}; border-radius: 25px;"></div>
934
- </div>
935
- </div>
936
- """, unsafe_allow_html=True)
937
-
938
- with col2:
939
- st.subheader("🏷️ Extracted Entities")
940
-
941
- if entities.get('companies'):
942
- st.write("**Companies:** " + ", ".join(entities['companies']))
943
- if entities.get('stocks'):
944
- st.write("**Stock Symbols:** " + ", ".join(entities['stocks']))
945
- if entities.get('sectors'):
946
- st.write("**Sectors:** " + ", ".join(entities['sectors']))
947
- if entities.get('metrics'):
948
- st.write("**Financial Metrics:** " + ", ".join(entities['metrics']))
949
-
950
- # Risk indicators
951
- st.subheader("⚠️ Risk Assessment")
952
- risk_class = f"risk-{trading_signal['risk_level'].lower()}"
953
- st.markdown(f'<span class="risk-indicator {risk_class}">{trading_signal["risk_level"]} Risk</span>', unsafe_allow_html=True)
954
-
955
- # Dashboard visualization
956
- st.subheader("πŸ“Š Interactive Dashboard")
957
- dashboard_fig = create_sentiment_dashboard(analysis_result, entities, trading_signal)
958
- if dashboard_fig:
959
- st.plotly_chart(dashboard_fig, use_container_width=True)
960
-
961
- # Trading recommendations
962
- st.subheader("πŸ’‘ Trading Recommendations")
963
-
964
- recommendations = []
965
-
966
- if trading_signal['signal'] == 'BUY':
967
- recommendations.extend([
968
- f"βœ… Consider opening a long position with {trading_signal['confidence']:.1%} confidence",
969
- f"🎯 Recommended position size: ${recommended_position:,.0f}",
970
- f"⏰ Time horizon: {investment_horizon}",
971
- "πŸ“Š Monitor for confirmation signals in next 24-48 hours"
972
- ])
973
- elif trading_signal['signal'] == 'SELL':
974
- recommendations.extend([
975
- f"❌ Consider reducing exposure or opening short position",
976
- f"πŸ›‘οΈ Implement stop-loss at current levels",
977
- f"⚠️ High risk scenario - monitor closely",
978
- "πŸ“‰ Consider defensive positioning"
979
- ])
980
- else:
981
- recommendations.extend([
982
- f"⏸️ Hold current positions - mixed signals detected",
983
- f"πŸ‘€ Wait for clearer market direction",
984
- f"πŸ“Š Monitor for breakthrough above {sentiment_threshold:.1%} confidence",
985
- "πŸ”„ Re-evaluate in 24-48 hours"
986
- ])
987
-
988
- for rec in recommendations:
989
- st.write(rec)
990
-
991
- # Export options
992
- st.subheader("πŸ“₯ Export & Alerts")
993
- col1, col2, col3 = st.columns(3)
994
-
995
- with col1:
996
- if st.button("πŸ“Š Export Report"):
997
- report_data = {
998
- 'timestamp': datetime.now().isoformat(),
999
- 'news_text': news_text[:200] + "...",
1000
- 'primary_sentiment': analysis_result['primary_sentiment'],
1001
- 'confidence': analysis_result['confidence'],
1002
- 'trading_signal': trading_signal['signal'],
1003
- 'risk_level': trading_signal['risk_level'],
1004
- 'recommended_position': recommended_position
1005
- }
1006
-
1007
- df = pd.DataFrame([report_data])
1008
- csv = df.to_csv(index=False)
1009
- st.download_button(
1010
- "πŸ“₯ Download Analysis Report",
1011
- csv,
1012
- f"financial_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
1013
- "text/csv"
1014
- )
1015
-
1016
- with col2:
1017
- if st.button("πŸ”” Setup Price Alert"):
1018
- st.success("Price alert configured for significant moves!")
1019
-
1020
- with col3:
1021
- if st.button("πŸ“§ Email Report"):
1022
- st.success("Report emailed to your registered address!")
1023
-
1024
- elif analysis_type == "Portfolio Impact Analysis":
1025
- st.header("πŸ’Ό Portfolio Impact Analysis")
1026
- st.info("🚧 Feature coming soon - Analyze news impact on your entire portfolio")
1027
-
1028
- # Portfolio input section
1029
- st.subheader("πŸ“Š Your Portfolio")
1030
- portfolio_input = st.text_area(
1031
- "Enter your holdings (Symbol: Quantity):",
1032
- placeholder="AAPL: 100\nTSLA: 50\nMSFT: 75",
1033
- height=150
1034
- )
1035
-
1036
- if st.button("πŸ“ˆ Analyze Portfolio Impact"):
1037
- st.success("Portfolio analysis feature will be available in the next update!")
1038
-
1039
- elif analysis_type == "Market Sector Analysis":
1040
- st.header("🏭 Market Sector Analysis")
1041
- st.info("🚧 Feature coming soon - Comprehensive sector sentiment analysis")
1042
-
1043
- sector = st.selectbox(
1044
- "Select Sector:",
1045
- ["Technology", "Healthcare", "Finance", "Energy", "Consumer Goods", "Industrial", "Real Estate"]
1046
- )
1047
-
1048
- if st.button("πŸ” Analyze Sector"):
1049
- st.success("Sector analysis feature will be available in the next update!")
1050
-
1051
- else: # Real-time News Feed
1052
- st.header("πŸ“‘ Real-time News Feed Analysis")
1053
- st.info("🚧 Feature coming soon - Live news sentiment monitoring")
1054
-
1055
- if st.button("πŸ”„ Start Live Monitoring"):
1056
- st.success("Live monitoring feature will be available in the next update!")
1057
-
1058
- # Footer
1059
- st.markdown("---")
1060
- st.markdown("""
1061
- <div style='text-align: center; color: #666; margin-top: 2rem;'>
1062
- <p><strong>⚠️ Disclaimer:</strong> This analysis is for informational purposes only and should not be considered as financial advice.</p>
1063
- <p>Always consult with a qualified financial advisor before making investment decisions.</p>
1064
- <p>πŸ€– Powered by Advanced AI β€’ Built for Professional Traders & Investors</p>
1065
- </div>
1066
- """, unsafe_allow_html=True)
1067
-
1068
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/telegram_bot.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ from typing import Any
4
+
5
+ from telegram import Update
6
+ from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
7
+ from dotenv import load_dotenv
8
+ from src.financial_news_requester import fetch_comp_financial_news
9
+
10
+
11
+ load_dotenv()
12
+
13
+ TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
14
+
15
+
16
+ def format_news_for_telegram(news_json: list[dict[str, Any]]) -> str:
17
+ message = ""
18
+ for item in news_json:
19
+ message += (
20
+ f"πŸ“° <b>{item.get('headline')}</b>\n"
21
+ f"πŸ“ {item.get('summary')}\n"
22
+ f"🏷️ Source: {item.get('source')}\n"
23
+ f"πŸ”— <a href=\"{item.get('url')}\">Read more</a>\n\n"
24
+ )
25
+ return message
26
+
27
+
28
+ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
29
+ await update.message.reply_text("Hello! I'm your Financial Bot.")
30
+
31
+ async def run_crew(update: Update, context: ContextTypes.DEFAULT_TYPE):
32
+ await update.message.reply_text("Running ...")
33
+ try:
34
+ feed = fetch_comp_financial_news()
35
+ logging.info(f"processed: {feed} news")
36
+ await update.message.reply_text(f"Result:\n{format_news_for_telegram(feed)}", parse_mode='HTML')
37
+ except Exception as e:
38
+ await update.message.reply_text(f"Error: {e}")
39
+
40
+ if __name__ == "__main__":
41
+ logging.basicConfig(level=logging.INFO)
42
+ app = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
43
+ app.add_handler(CommandHandler("start", start))
44
+ app.add_handler(CommandHandler("run", run_crew))
45
+ app.run_polling()