Benjamin Consolvo commited on
Commit
15d1512
·
1 Parent(s): b19b670

sentiment logging

Browse files
Files changed (1) hide show
  1. app.py +43 -20
app.py CHANGED
@@ -128,12 +128,12 @@ class NewsSentiment:
128
  def __init__(self, API_KEY):
129
  self.newsapi = NewsApiClient(api_key=API_KEY)
130
  self.sia = SentimentIntensityAnalyzer()
131
- self.alpha_vantage_api_key = st.secrets.get("ALPHA_VANTAGE_API_KEY") # Add your Alpha Vantage key to Streamlit secrets
132
 
133
  def get_sentiment_and_headlines(self, symbol):
134
  """
135
  Try NewsAPI first, fallback to Alpha Vantage if needed.
136
- Returns (sentiment, headlines).
137
  """
138
  # Try NewsAPI
139
  try:
@@ -141,7 +141,8 @@ class NewsSentiment:
141
  headlines = [a['title'] for a in articles.get('articles', [])[:5]]
142
  if headlines:
143
  sentiment = self._calculate_sentiment(headlines)
144
- return sentiment, headlines
 
145
  else:
146
  logger.warning(f"NewsAPI returned no headlines for {symbol}.")
147
  except Exception as e:
@@ -152,7 +153,7 @@ class NewsSentiment:
152
  try:
153
  if not self.alpha_vantage_api_key:
154
  logger.error("Alpha Vantage API key not found in Streamlit secrets.")
155
- return None, []
156
  import requests
157
  url = (
158
  f"https://www.alphavantage.co/query?function=NEWS_SENTIMENT&tickers={symbol}"
@@ -160,12 +161,11 @@ class NewsSentiment:
160
  )
161
  resp = requests.get(url)
162
  data = resp.json()
163
- # Alpha Vantage returns a list of news items under "feed"
164
  headlines = [item.get("title") for item in data.get("feed", [])[:5] if item.get("title")]
165
  if headlines:
166
- logger.info(f"Using Alpha Vantage headlines for {symbol}: {headlines}")
167
  sentiment = self._calculate_sentiment(headlines)
168
- return sentiment, headlines
 
169
  else:
170
  logger.warning(f"Alpha Vantage returned no headlines for {symbol}.")
171
  except Exception as e:
@@ -174,7 +174,7 @@ class NewsSentiment:
174
  logger.info(
175
  f"No sentiment/headlines available for {symbol} from either NewsAPI or Alpha Vantage."
176
  )
177
- return None, []
178
 
179
  def _calculate_sentiment(self, headlines):
180
  if not headlines:
@@ -191,17 +191,26 @@ class NewsSentiment:
191
  def get_sentiment_bulk(self, symbols):
192
  """
193
  Bulk sentiment for a list of symbols using NewsAPI only (for auto-trade).
194
- Returns dict: symbol -> sentiment.
195
  """
196
  sentiment = {}
197
  for symbol in symbols:
198
  try:
199
  articles = self.newsapi.get_everything(q=symbol, language='en', sort_by='publishedAt', page=1)
200
  headlines = [a['title'] for a in articles.get('articles', [])[:5]]
201
- sentiment[symbol] = self._calculate_sentiment(headlines) if headlines else 'Neutral'
 
 
 
 
 
 
 
202
  except Exception as e:
203
  logger.error(f"Error getting news for {symbol}: {e}")
204
- sentiment[symbol] = 'Neutral'
 
 
205
  return sentiment
206
 
207
 
@@ -342,7 +351,7 @@ class TradingApp:
342
  self.sentiment = NewsSentiment(st.secrets['NEWS_API_KEY'])
343
  self.analyzer = StockAnalyzer(self.alpaca)
344
  self.data = self.analyzer.get_historical_data(self.analyzer.symbols)
345
- self.auto_trade_log = [] # Store automatic trade actions
346
 
347
  def display_charts(self):
348
  # Dynamically adjust columns based on number of stocks and available width
@@ -403,24 +412,29 @@ class TradingApp:
403
  st.session_state["sentiment_result"] = None
404
  if "article_headlines" not in st.session_state:
405
  st.session_state["article_headlines"] = []
 
 
406
 
407
  if st.button("Check Sentiment"):
408
  if symbol:
409
- sentiment_result, article_headlines = self.sentiment.get_sentiment_and_headlines(symbol)
410
  st.session_state["sentiment_result"] = sentiment_result
411
  st.session_state["article_headlines"] = article_headlines
412
  st.session_state["sentiment_symbol"] = symbol
 
413
  else:
414
  st.session_state["sentiment_result"] = None
415
  st.session_state["article_headlines"] = []
416
  st.session_state["sentiment_symbol"] = ""
 
417
 
418
  sentiment_result = st.session_state.get("sentiment_result")
419
  article_headlines = st.session_state.get("article_headlines", [])
420
  sentiment_symbol = st.session_state.get("sentiment_symbol", "")
 
421
 
422
  if symbol and sentiment_symbol == symbol and sentiment_result is not None:
423
- st.markdown(f"**Sentiment for {symbol.upper()}:** {sentiment_result if sentiment_result in ['Positive', 'Negative', 'Neutral'] else 'No sentiment available'}")
424
  elif symbol and sentiment_symbol == symbol and sentiment_result is None:
425
  st.markdown("**Sentiment:** No sentiment available")
426
 
@@ -518,17 +532,23 @@ class TradingApp:
518
  def _execute_sentiment_trades(self, sentiment):
519
  actions = []
520
  symbol_to_name = self.analyzer.symbol_to_name
521
- for symbol, sentiment_value in sentiment.items():
 
 
 
 
 
522
  # If sentiment is missing or invalid, try to get it using fallback
523
  if sentiment_value is None or sentiment_value not in ['Positive', 'Negative', 'Neutral']:
524
- sentiment_value, _ = self.sentiment.get_sentiment_and_headlines(symbol)
525
  action = None
526
  is_market_open = self.alpaca.get_market_status()
 
527
  if sentiment_value == 'Positive':
528
- order = self.alpaca.buy(symbol, 1, reason="Sentiment: Positive")
529
  action = 'Buy'
530
  elif sentiment_value == 'Negative':
531
- order = self.alpaca.sell(symbol, 1, reason="Sentiment: Negative")
532
  action = 'Sell'
533
  else:
534
  order = None
@@ -547,6 +567,7 @@ class TradingApp:
547
  'symbol': symbol,
548
  'company_name': symbol_to_name.get(symbol, ''),
549
  'sentiment': sentiment_value,
 
550
  'action': action
551
  })
552
  return actions
@@ -555,7 +576,7 @@ def background_auto_trade(app):
555
  """This function runs in a background thread and updates session state with automatic trades."""
556
  while True:
557
  start_time = time.time()
558
- # Use NewsAPI only for bulk sentiment (to avoid rate limits and speed)
559
  sentiment = app.sentiment.get_sentiment_bulk(app.analyzer.symbols)
560
  actions = app._execute_sentiment_trades(sentiment)
561
  log_entry = {
@@ -699,7 +720,9 @@ def main():
699
  st.write(f"Last checked: {last_entry['timestamp']}")
700
  df = pd.DataFrame(last_entry["actions"])
701
  if "company_name" in df.columns:
702
- df = df[["symbol", "company_name", "sentiment", "action"]]
 
 
703
  st.dataframe(df)
704
  st.write("Sentiment Analysis (latest):")
705
  st.write(last_entry["sentiment"])
 
128
  def __init__(self, API_KEY):
129
  self.newsapi = NewsApiClient(api_key=API_KEY)
130
  self.sia = SentimentIntensityAnalyzer()
131
+ self.alpha_vantage_api_key = st.secrets.get("ALPHA_VANTAGE_API_KEY")
132
 
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:
 
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
  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}"
 
161
  )
162
  resp = requests.get(url)
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
  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:
 
191
  def get_sentiment_bulk(self, symbols):
192
  """
193
  Bulk sentiment for a list of symbols using NewsAPI only (for auto-trade).
194
+ Returns dict: symbol -> (sentiment, source).
195
  """
196
  sentiment = {}
197
  for symbol in symbols:
198
  try:
199
  articles = self.newsapi.get_everything(q=symbol, language='en', sort_by='publishedAt', page=1)
200
  headlines = [a['title'] for a in articles.get('articles', [])[:5]]
201
+ if headlines:
202
+ s = self._calculate_sentiment(headlines)
203
+ logger.info(f"NewsAPI sentiment for {symbol}: {s} | Headlines: {headlines}")
204
+ sentiment[symbol] = (s, "NewsAPI")
205
+ else:
206
+ # fallback to Alpha Vantage
207
+ s, _, src = self.get_sentiment_and_headlines(symbol)
208
+ sentiment[symbol] = (s, src)
209
  except Exception as e:
210
  logger.error(f"Error getting news for {symbol}: {e}")
211
+ # fallback to Alpha Vantage
212
+ s, _, src = self.get_sentiment_and_headlines(symbol)
213
+ sentiment[symbol] = (s, src)
214
  return sentiment
215
 
216
 
 
351
  self.sentiment = NewsSentiment(st.secrets['NEWS_API_KEY'])
352
  self.analyzer = StockAnalyzer(self.alpaca)
353
  self.data = self.analyzer.get_historical_data(self.analyzer.symbols)
354
+ self.auto_trade_log = []
355
 
356
  def display_charts(self):
357
  # Dynamically adjust columns based on number of stocks and available width
 
412
  st.session_state["sentiment_result"] = None
413
  if "article_headlines" not in st.session_state:
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
 
 
532
  def _execute_sentiment_trades(self, sentiment):
533
  actions = []
534
  symbol_to_name = self.analyzer.symbol_to_name
535
+ for symbol, sentiment_info in sentiment.items():
536
+ # sentiment_info is now (sentiment, source)
537
+ if isinstance(sentiment_info, tuple):
538
+ sentiment_value, sentiment_source = sentiment_info
539
+ else:
540
+ sentiment_value, sentiment_source = sentiment_info, None
541
  # If sentiment is missing or invalid, try to get it using fallback
542
  if sentiment_value is None or sentiment_value not in ['Positive', 'Negative', 'Neutral']:
543
+ sentiment_value, _, sentiment_source = self.sentiment.get_sentiment_and_headlines(symbol)
544
  action = None
545
  is_market_open = self.alpaca.get_market_status()
546
+ logger.info(f"Auto-trade: {symbol} | Sentiment: {sentiment_value} | Source: {sentiment_source}")
547
  if sentiment_value == 'Positive':
548
+ order = self.alpaca.buy(symbol, 1, reason=f"Sentiment: Positive ({sentiment_source})")
549
  action = 'Buy'
550
  elif sentiment_value == 'Negative':
551
+ order = self.alpaca.sell(symbol, 1, reason=f"Sentiment: Negative ({sentiment_source})")
552
  action = 'Sell'
553
  else:
554
  order = None
 
567
  'symbol': symbol,
568
  'company_name': symbol_to_name.get(symbol, ''),
569
  'sentiment': sentiment_value,
570
+ 'sentiment_source': sentiment_source,
571
  'action': action
572
  })
573
  return actions
 
576
  """This function runs in a background thread and updates session state with automatic trades."""
577
  while True:
578
  start_time = time.time()
579
+ # Use NewsAPI and Alpha Vantage for bulk sentiment (with fallback)
580
  sentiment = app.sentiment.get_sentiment_bulk(app.analyzer.symbols)
581
  actions = app._execute_sentiment_trades(sentiment)
582
  log_entry = {
 
720
  st.write(f"Last checked: {last_entry['timestamp']}")
721
  df = pd.DataFrame(last_entry["actions"])
722
  if "company_name" in df.columns:
723
+ # Show sentiment source if available
724
+ display_cols = ["symbol", "company_name", "sentiment", "sentiment_source", "action"] if "sentiment_source" in df.columns else ["symbol", "company_name", "sentiment", "action"]
725
+ df = df[display_cols]
726
  st.dataframe(df)
727
  st.write("Sentiment Analysis (latest):")
728
  st.write(last_entry["sentiment"])