Spaces:
Running
Running
Benjamin Consolvo
commited on
Commit
·
9c621d2
1
Parent(s):
3e83ec5
background trading on sentiment updating in UI
Browse files
app.py
CHANGED
@@ -147,7 +147,7 @@ class NewsSentiment:
|
|
147 |
sort_by='publishedAt', # <-- fixed argument name
|
148 |
page=1)
|
149 |
compound_score = 0
|
150 |
-
for article in articles['articles'][:5]
|
151 |
# print(f'article= {article}')
|
152 |
score = self.sia.polarity_scores(article['title'])['compound']
|
153 |
compound_score += score
|
@@ -182,6 +182,10 @@ class StockAnalyzer:
|
|
182 |
bars_data = {}
|
183 |
try:
|
184 |
bars = alp_api.get_bars(list(symbols), timeframe).df
|
|
|
|
|
|
|
|
|
185 |
for symbol in symbols:
|
186 |
symbol_bars = bars[bars['symbol'] == symbol]
|
187 |
if not symbol_bars.empty:
|
@@ -202,7 +206,6 @@ class StockAnalyzer:
|
|
202 |
}
|
203 |
}
|
204 |
else:
|
205 |
-
# Only log at debug level to avoid spamming warnings for missing bar data
|
206 |
logger.debug(f"No bar data for symbol: {symbol}")
|
207 |
bars_data[symbol] = {'bar_data': None}
|
208 |
except Exception as e:
|
@@ -434,11 +437,19 @@ class TradingApp:
|
|
434 |
st.button("Refresh Portfolio", on_click=refresh_portfolio)
|
435 |
|
436 |
def auto_trade_based_on_sentiment(self, sentiment):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
actions = []
|
438 |
symbol_to_name = self.analyzer.symbol_to_name
|
439 |
for symbol, sentiment_value in sentiment.items():
|
440 |
action = None
|
441 |
-
is_market_open = self.alpaca.get_market_status()
|
442 |
if sentiment_value == 'Positive':
|
443 |
order = self.alpaca.buy(symbol, 1, reason="Sentiment: Positive")
|
444 |
action = 'Buy'
|
@@ -464,43 +475,17 @@ class TradingApp:
|
|
464 |
'sentiment': sentiment_value,
|
465 |
'action': action
|
466 |
})
|
467 |
-
self.auto_trade_log = actions
|
468 |
return actions
|
469 |
|
470 |
def background_auto_trade(app):
|
471 |
-
|
472 |
while True:
|
|
|
|
|
473 |
sentiment = app.sentiment.get_news_sentiment(app.analyzer.symbols)
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
action = None
|
478 |
-
is_market_open = app.alpaca.get_market_status()
|
479 |
-
if sentiment_value == 'Positive':
|
480 |
-
order = app.alpaca.buy(symbol, 1, reason="Sentiment: Positive")
|
481 |
-
action = 'Buy'
|
482 |
-
elif sentiment_value == 'Negative':
|
483 |
-
order = app.alpaca.sell(symbol, 1, reason="Sentiment: Negative")
|
484 |
-
action = 'Sell'
|
485 |
-
else:
|
486 |
-
order = None
|
487 |
-
action = 'Hold'
|
488 |
-
logger.info(f"Held {symbol}")
|
489 |
-
|
490 |
-
if order:
|
491 |
-
if not is_market_open:
|
492 |
-
_, _, next_open, _ = get_market_times(app.alpaca.alpaca)
|
493 |
-
next_open_time = next_open.strftime('%Y-%m-%d %H:%M:%S') if next_open else "unknown"
|
494 |
-
logger.warning(f"Market is currently closed. The {action.lower} order for 1 share of {symbol} has been submitted and will execute when the market opens at {next_open_time}.")
|
495 |
-
else:
|
496 |
-
logger.info(f"Order executed: {action} 1 share of {symbol}")
|
497 |
-
|
498 |
-
actions.append({
|
499 |
-
'symbol': symbol,
|
500 |
-
'company_name': symbol_to_name.get(symbol, ''),
|
501 |
-
'sentiment': sentiment_value,
|
502 |
-
'action': action
|
503 |
-
})
|
504 |
|
505 |
# Create log entry
|
506 |
log_entry = {
|
@@ -509,39 +494,30 @@ def background_auto_trade(app):
|
|
509 |
"sentiment": sentiment
|
510 |
}
|
511 |
|
512 |
-
# Update session state -
|
513 |
-
if
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
if len(st.session_state[AUTO_TRADE_LOG_KEY]) > 50:
|
532 |
-
st.session_state[AUTO_TRADE_LOG_KEY] = st.session_state[AUTO_TRADE_LOG_KEY][-50:]
|
533 |
|
534 |
def get_auto_trade_log():
|
535 |
-
"""Get the auto trade log from session state"""
|
536 |
if AUTO_TRADE_LOG_KEY not in st.session_state:
|
537 |
st.session_state[AUTO_TRADE_LOG_KEY] = []
|
538 |
-
|
539 |
-
# Check if we have a new entry from background thread
|
540 |
-
if "latest_auto_trade_entry" in st.session_state:
|
541 |
-
update_auto_trade_log(st.session_state["latest_auto_trade_entry"])
|
542 |
-
# Clear the latest entry after adding it
|
543 |
-
del st.session_state["latest_auto_trade_entry"]
|
544 |
-
|
545 |
return st.session_state[AUTO_TRADE_LOG_KEY]
|
546 |
|
547 |
def get_market_times(alpaca_api):
|
@@ -561,7 +537,7 @@ def main():
|
|
561 |
st.markdown("This is a fun stock trading application that uses Alpaca API for trading and News API for sentiment analysis. Come and trade my money! Well, it's a paper account, so it's not real money. But still, have fun!")
|
562 |
|
563 |
if not st.secrets['ALPACA_API_KEY'] or not st.secrets['NEWS_API_KEY']:
|
564 |
-
st.error("Please configure your
|
565 |
return
|
566 |
|
567 |
# Prevent Streamlit from rerunning the script on every widget interaction
|
|
|
147 |
sort_by='publishedAt', # <-- fixed argument name
|
148 |
page=1)
|
149 |
compound_score = 0
|
150 |
+
for article in articles['articles'][:5] # Check first 5 articles
|
151 |
# print(f'article= {article}')
|
152 |
score = self.sia.polarity_scores(article['title'])['compound']
|
153 |
compound_score += score
|
|
|
182 |
bars_data = {}
|
183 |
try:
|
184 |
bars = alp_api.get_bars(list(symbols), timeframe).df
|
185 |
+
if 'symbol' not in bars.columns:
|
186 |
+
logger.warning("The 'symbol' column is missing in the bars DataFrame.")
|
187 |
+
return {symbol: {'bar_data': None} for symbol in symbols}
|
188 |
+
|
189 |
for symbol in symbols:
|
190 |
symbol_bars = bars[bars['symbol'] == symbol]
|
191 |
if not symbol_bars.empty:
|
|
|
206 |
}
|
207 |
}
|
208 |
else:
|
|
|
209 |
logger.debug(f"No bar data for symbol: {symbol}")
|
210 |
bars_data[symbol] = {'bar_data': None}
|
211 |
except Exception as e:
|
|
|
437 |
st.button("Refresh Portfolio", on_click=refresh_portfolio)
|
438 |
|
439 |
def auto_trade_based_on_sentiment(self, sentiment):
|
440 |
+
"""Execute trades based on sentiment analysis and return actions taken."""
|
441 |
+
actions = self._execute_sentiment_trades(sentiment)
|
442 |
+
self.auto_trade_log = actions
|
443 |
+
return actions
|
444 |
+
|
445 |
+
def _execute_sentiment_trades(self, sentiment):
|
446 |
+
"""Helper method to execute trades based on sentiment.
|
447 |
+
Used by both auto_trade_based_on_sentiment and background_auto_trade."""
|
448 |
actions = []
|
449 |
symbol_to_name = self.analyzer.symbol_to_name
|
450 |
for symbol, sentiment_value in sentiment.items():
|
451 |
action = None
|
452 |
+
is_market_open = self.alpaca.get_market_status()
|
453 |
if sentiment_value == 'Positive':
|
454 |
order = self.alpaca.buy(symbol, 1, reason="Sentiment: Positive")
|
455 |
action = 'Buy'
|
|
|
475 |
'sentiment': sentiment_value,
|
476 |
'action': action
|
477 |
})
|
|
|
478 |
return actions
|
479 |
|
480 |
def background_auto_trade(app):
|
481 |
+
"""This function runs in a background thread and updates session state with automatic trades."""
|
482 |
while True:
|
483 |
+
start_time = time.time() # Record the start time of the iteration
|
484 |
+
|
485 |
sentiment = app.sentiment.get_news_sentiment(app.analyzer.symbols)
|
486 |
+
|
487 |
+
# Use the shared method to execute trades
|
488 |
+
actions = app._execute_sentiment_trades(sentiment)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
489 |
|
490 |
# Create log entry
|
491 |
log_entry = {
|
|
|
494 |
"sentiment": sentiment
|
495 |
}
|
496 |
|
497 |
+
# Update session state - ensure the UI reflects the latest data
|
498 |
+
if AUTO_TRADE_LOG_KEY not in st.session_state:
|
499 |
+
st.session_state[AUTO_TRADE_LOG_KEY] = []
|
500 |
+
|
501 |
+
st.session_state[AUTO_TRADE_LOG_KEY].append(log_entry)
|
502 |
+
|
503 |
+
# Limit size to avoid memory issues (keep last 50 entries)
|
504 |
+
if len(st.session_state[AUTO_TRADE_LOG_KEY]) > 50:
|
505 |
+
st.session_state[AUTO_TRADE_LOG_KEY] = st.session_state[AUTO_TRADE_LOG_KEY][-50:]
|
506 |
+
|
507 |
+
# Log the update
|
508 |
+
logger.info(f"Auto-trade completed. Actions: {actions}")
|
509 |
+
|
510 |
+
# Calculate the time taken for this iteration
|
511 |
+
elapsed_time = time.time() - start_time
|
512 |
+
sleep_time = max(0, AUTO_TRADE_INTERVAL - elapsed_time) # Ensure non-negative sleep time
|
513 |
+
|
514 |
+
logger.info(f"Sleeping for {sleep_time:.2f} seconds before the next auto-trade.")
|
515 |
+
time.sleep(sleep_time)
|
|
|
|
|
516 |
|
517 |
def get_auto_trade_log():
|
518 |
+
"""Get the auto trade log from session state."""
|
519 |
if AUTO_TRADE_LOG_KEY not in st.session_state:
|
520 |
st.session_state[AUTO_TRADE_LOG_KEY] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
521 |
return st.session_state[AUTO_TRADE_LOG_KEY]
|
522 |
|
523 |
def get_market_times(alpaca_api):
|
|
|
537 |
st.markdown("This is a fun stock trading application that uses Alpaca API for trading and News API for sentiment analysis. Come and trade my money! Well, it's a paper account, so it's not real money. But still, have fun!")
|
538 |
|
539 |
if not st.secrets['ALPACA_API_KEY'] or not st.secrets['NEWS_API_KEY']:
|
540 |
+
st.error("Please configure your ALPACA_API_KEY and NEWS_API_KEY")
|
541 |
return
|
542 |
|
543 |
# Prevent Streamlit from rerunning the script on every widget interaction
|