Spaces:
Running
Running
Benjamin Consolvo
commited on
Commit
·
147b89a
1
Parent(s):
757bcb4
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
|