Benjamin Consolvo commited on
Commit
7919ea6
·
1 Parent(s): 9fd65b3

reduced font news headlines

Browse files
Files changed (1) hide show
  1. app.py +34 -22
app.py CHANGED
@@ -133,16 +133,16 @@ class NewsSentiment:
133
  def get_sentiment_and_headlines(self, symbol):
134
  """
135
  Try NewsAPI first, fallback to Alpha Vantage if needed.
136
- Returns (sentiment, headlines, source).
137
  """
138
  # Try NewsAPI
139
  try:
140
  articles = self.newsapi.get_everything(q=symbol, language='en', sort_by='publishedAt', page=1)
141
  headlines = [a['title'] for a in articles.get('articles', [])[:5]]
142
  if headlines:
143
- sentiment = self._calculate_sentiment(headlines)
144
  logger.info(f"NewsAPI sentiment for {symbol}: {sentiment} | Headlines: {headlines}")
145
- return sentiment, headlines, "NewsAPI"
146
  else:
147
  logger.warning(f"NewsAPI returned no headlines for {symbol}.")
148
  except Exception as e:
@@ -153,7 +153,7 @@ class NewsSentiment:
153
  try:
154
  if not self.alpha_vantage_api_key:
155
  logger.error("Alpha Vantage API key not found in Streamlit secrets.")
156
- return None, [], "AlphaVantage"
157
  import requests
158
  url = (
159
  f"https://www.alphavantage.co/query?function=NEWS_SENTIMENT&tickers={symbol}"
@@ -163,9 +163,9 @@ class NewsSentiment:
163
  data = resp.json()
164
  headlines = [item.get("title") for item in data.get("feed", [])[:5] if item.get("title")]
165
  if headlines:
166
- sentiment = self._calculate_sentiment(headlines)
167
  logger.info(f"Alpha Vantage sentiment for {symbol}: {sentiment} | Headlines: {headlines}")
168
- return sentiment, headlines, "AlphaVantage"
169
  else:
170
  logger.warning(f"Alpha Vantage returned no headlines for {symbol}.")
171
  except Exception as e:
@@ -174,19 +174,20 @@ class NewsSentiment:
174
  logger.info(
175
  f"No sentiment/headlines available for {symbol} from either NewsAPI or Alpha Vantage."
176
  )
177
- return None, [], None
178
 
179
- def _calculate_sentiment(self, headlines):
180
  if not headlines:
181
- return None
182
  compound_score = sum(self.sia.polarity_scores(title)['compound'] for title in headlines)
183
  avg_score = compound_score / len(headlines)
184
  if avg_score > 0.1:
185
- return 'Positive'
186
  elif avg_score < -0.1:
187
- return 'Negative'
188
  else:
189
- return 'Neutral'
 
190
 
191
  def get_sentiment_bulk(self, symbols):
192
  """
@@ -414,39 +415,45 @@ class TradingApp:
414
  st.session_state["article_headlines"] = []
415
  if "sentiment_source" not in st.session_state:
416
  st.session_state["sentiment_source"] = None
 
 
417
 
418
  if st.button("Check Sentiment"):
419
  if symbol:
420
- sentiment_result, article_headlines, sentiment_source = self.sentiment.get_sentiment_and_headlines(symbol)
421
  st.session_state["sentiment_result"] = sentiment_result
422
  st.session_state["article_headlines"] = article_headlines
423
  st.session_state["sentiment_symbol"] = symbol
424
  st.session_state["sentiment_source"] = sentiment_source
 
425
  else:
426
  st.session_state["sentiment_result"] = None
427
  st.session_state["article_headlines"] = []
428
  st.session_state["sentiment_symbol"] = ""
429
  st.session_state["sentiment_source"] = None
 
430
 
431
  sentiment_result = st.session_state.get("sentiment_result")
432
  article_headlines = st.session_state.get("article_headlines", [])
433
  sentiment_symbol = st.session_state.get("sentiment_symbol", "")
434
  sentiment_source = st.session_state.get("sentiment_source", "")
 
435
 
436
  if symbol and sentiment_symbol == symbol and sentiment_result is not None:
437
  st.markdown(f"**Sentiment for {symbol.upper()} ({sentiment_source}):** {sentiment_result if sentiment_result in ['Positive', 'Negative', 'Neutral'] else 'No sentiment available'}")
 
 
 
438
  elif symbol and sentiment_symbol == symbol and sentiment_result is None:
439
  st.markdown("**Sentiment:** No sentiment available")
440
 
441
- # Shrink headlines font and remove bullets, add clickable links
442
  if symbol and sentiment_symbol == symbol and article_headlines:
443
  st.markdown(
444
- "<div style='font-size: 0.85em; margin-bottom: 0.5em;'><b>Recent Headlines:</b></div>",
445
  unsafe_allow_html=True
446
  )
447
  # Try to get URLs for headlines if available
448
- # Fetch the latest headlines and URLs from the same source as get_sentiment_and_headlines
449
- # For NewsAPI, get 'url' from articles; for Alpha Vantage, get 'url' from feed
450
  headlines_with_links = []
451
  try:
452
  if sentiment_source == "NewsAPI":
@@ -468,21 +475,26 @@ class TradingApp:
468
  (item.get("title"), item.get("url")) for item in feed if item.get("title")
469
  ]
470
  else:
471
- # fallback: just show headlines without links
472
  headlines_with_links = [(headline, None) for headline in article_headlines]
473
  except Exception as e:
474
  logger.error(f"Error fetching URLs for headlines: {e}")
475
  headlines_with_links = [(headline, None) for headline in article_headlines]
476
 
477
- for headline, url in headlines_with_links:
 
478
  if url:
479
  st.markdown(
480
- f"<div style='font-size: 0.85em; margin-bottom: 0.2em; color: #444;'><a href='{url}' target='_blank' style='color: #444; text-decoration: underline;'>{headline}</a></div>",
 
 
 
481
  unsafe_allow_html=True
482
  )
483
  else:
484
  st.markdown(
485
- f"<div style='font-size: 0.85em; margin-bottom: 0.2em; color: #444;'>{headline}</div>",
 
 
486
  unsafe_allow_html=True
487
  )
488
  elif symbol and sentiment_symbol == symbol and sentiment_result is not None and not article_headlines:
@@ -658,7 +670,7 @@ def get_market_times(alpaca_api):
658
 
659
  def main():
660
  st.title("Ben's Stock Trading Application")
661
- st.markdown("This is a fun stock trading application that uses a combination of key frameworks like Alpaca API, yfinance, and News API for stock information and trading. Come and trade my money! Well, it's a paper account, so it's not real money. But still, have fun!")
662
 
663
  if not st.secrets['ALPACA_API_KEY'] or not st.secrets['NEWS_API_KEY']:
664
  st.error("Please configure your ALPACA_API_KEY and NEWS_API_KEY")
 
133
  def get_sentiment_and_headlines(self, symbol):
134
  """
135
  Try NewsAPI first, fallback to Alpha Vantage if needed.
136
+ Returns (sentiment, headlines, source, avg_score).
137
  """
138
  # Try NewsAPI
139
  try:
140
  articles = self.newsapi.get_everything(q=symbol, language='en', sort_by='publishedAt', page=1)
141
  headlines = [a['title'] for a in articles.get('articles', [])[:5]]
142
  if headlines:
143
+ sentiment, avg_score = self._calculate_sentiment(headlines, return_score=True)
144
  logger.info(f"NewsAPI sentiment for {symbol}: {sentiment} | Headlines: {headlines}")
145
+ return sentiment, headlines, "NewsAPI", avg_score
146
  else:
147
  logger.warning(f"NewsAPI returned no headlines for {symbol}.")
148
  except Exception as e:
 
153
  try:
154
  if not self.alpha_vantage_api_key:
155
  logger.error("Alpha Vantage API key not found in Streamlit secrets.")
156
+ return None, [], "AlphaVantage", None
157
  import requests
158
  url = (
159
  f"https://www.alphavantage.co/query?function=NEWS_SENTIMENT&tickers={symbol}"
 
163
  data = resp.json()
164
  headlines = [item.get("title") for item in data.get("feed", [])[:5] if item.get("title")]
165
  if headlines:
166
+ sentiment, avg_score = self._calculate_sentiment(headlines, return_score=True)
167
  logger.info(f"Alpha Vantage sentiment for {symbol}: {sentiment} | Headlines: {headlines}")
168
+ return sentiment, headlines, "AlphaVantage", avg_score
169
  else:
170
  logger.warning(f"Alpha Vantage returned no headlines for {symbol}.")
171
  except Exception as e:
 
174
  logger.info(
175
  f"No sentiment/headlines available for {symbol} from either NewsAPI or Alpha Vantage."
176
  )
177
+ return None, [], None, None
178
 
179
+ def _calculate_sentiment(self, headlines, return_score=False):
180
  if not headlines:
181
+ return (None, None) if return_score else None
182
  compound_score = sum(self.sia.polarity_scores(title)['compound'] for title in headlines)
183
  avg_score = compound_score / len(headlines)
184
  if avg_score > 0.1:
185
+ sentiment = 'Positive'
186
  elif avg_score < -0.1:
187
+ sentiment = 'Negative'
188
  else:
189
+ sentiment = 'Neutral'
190
+ return (sentiment, avg_score) if return_score else sentiment
191
 
192
  def get_sentiment_bulk(self, symbols):
193
  """
 
415
  st.session_state["article_headlines"] = []
416
  if "sentiment_source" not in st.session_state:
417
  st.session_state["sentiment_source"] = None
418
+ if "sentiment_score" not in st.session_state:
419
+ st.session_state["sentiment_score"] = None
420
 
421
  if st.button("Check Sentiment"):
422
  if symbol:
423
+ sentiment_result, article_headlines, sentiment_source, sentiment_score = self.sentiment.get_sentiment_and_headlines(symbol)
424
  st.session_state["sentiment_result"] = sentiment_result
425
  st.session_state["article_headlines"] = article_headlines
426
  st.session_state["sentiment_symbol"] = symbol
427
  st.session_state["sentiment_source"] = sentiment_source
428
+ st.session_state["sentiment_score"] = sentiment_score
429
  else:
430
  st.session_state["sentiment_result"] = None
431
  st.session_state["article_headlines"] = []
432
  st.session_state["sentiment_symbol"] = ""
433
  st.session_state["sentiment_source"] = None
434
+ st.session_state["sentiment_score"] = None
435
 
436
  sentiment_result = st.session_state.get("sentiment_result")
437
  article_headlines = st.session_state.get("article_headlines", [])
438
  sentiment_symbol = st.session_state.get("sentiment_symbol", "")
439
  sentiment_source = st.session_state.get("sentiment_source", "")
440
+ sentiment_score = st.session_state.get("sentiment_score", None)
441
 
442
  if symbol and sentiment_symbol == symbol and sentiment_result is not None:
443
  st.markdown(f"**Sentiment for {symbol.upper()} ({sentiment_source}):** {sentiment_result if sentiment_result in ['Positive', 'Negative', 'Neutral'] else 'No sentiment available'}")
444
+ # Show the actual average score below
445
+ if sentiment_score is not None:
446
+ st.markdown(f"<span style='font-size:0.8em;color:#888;'>Average sentiment score: <b>{sentiment_score:.3f}</b></span>", unsafe_allow_html=True)
447
  elif symbol and sentiment_symbol == symbol and sentiment_result is None:
448
  st.markdown("**Sentiment:** No sentiment available")
449
 
450
+ # Shrink headlines font, number them, and use dark blue/white with underline for links
451
  if symbol and sentiment_symbol == symbol and article_headlines:
452
  st.markdown(
453
+ "<div style='font-size: 0.75em; margin-bottom: 0.5em;'><b>Recent Headlines:</b></div>",
454
  unsafe_allow_html=True
455
  )
456
  # Try to get URLs for headlines if available
 
 
457
  headlines_with_links = []
458
  try:
459
  if sentiment_source == "NewsAPI":
 
475
  (item.get("title"), item.get("url")) for item in feed if item.get("title")
476
  ]
477
  else:
 
478
  headlines_with_links = [(headline, None) for headline in article_headlines]
479
  except Exception as e:
480
  logger.error(f"Error fetching URLs for headlines: {e}")
481
  headlines_with_links = [(headline, None) for headline in article_headlines]
482
 
483
+ # Use dark blue (#003366) for links, white for dark backgrounds (auto-detect not possible, so use blue)
484
+ for idx, (headline, url) in enumerate(headlines_with_links, 1):
485
  if url:
486
  st.markdown(
487
+ f"<div style='font-size:0.75em; margin-bottom:0.15em; color:#003366;'>"
488
+ f"<span style='font-weight:bold;'>{idx}.</span> "
489
+ f"<a href='{url}' target='_blank' style='color:#003366; text-decoration:underline;'>{headline}</a>"
490
+ f"</div>",
491
  unsafe_allow_html=True
492
  )
493
  else:
494
  st.markdown(
495
+ f"<div style='font-size:0.75em; margin-bottom:0.15em; color:#003366;'>"
496
+ f"<span style='font-weight:bold;'>{idx}.</span> {headline}"
497
+ f"</div>",
498
  unsafe_allow_html=True
499
  )
500
  elif symbol and sentiment_symbol == symbol and sentiment_result is not None and not article_headlines:
 
670
 
671
  def main():
672
  st.title("Ben's Stock Trading Application")
673
+ st.markdown("This is a fun stock trading application that uses a combination of key frameworks like Alpaca API, yfinance, News API, and Alpha Vantage for stock information and trading. Come and trade my money! Well, it's a paper account, so it's not real money. But still, have fun!")
674
 
675
  if not st.secrets['ALPACA_API_KEY'] or not st.secrets['NEWS_API_KEY']:
676
  st.error("Please configure your ALPACA_API_KEY and NEWS_API_KEY")