Benjamin Consolvo commited on
Commit
9873c68
·
1 Parent(s): dee760c

rm query_params()

Browse files
Files changed (1) hide show
  1. app.py +324 -2
app.py CHANGED
@@ -420,8 +420,330 @@ def load_auto_trade_log():
420
  except Exception:
421
  return None
422
 
423
- # Replace deprecated st.experimental_set_query_params with st.query_params
424
- st.query_params() # This is a no-op but ensures Streamlit doesn't rerun due to query params
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
  class TradingApp:
427
  def __init__(self):
 
420
  except Exception:
421
  return None
422
 
423
+ class TradingApp:
424
+ def __init__(self):
425
+ self.alpaca = AlpacaTrader(st.secrets['ALPACA_API_KEY'], st.secrets['ALPACA_SECRET_KEY'], 'https://paper-api.alpaca.markets')
426
+ self.sentiment = NewsSentiment(st.secrets['NEWS_API_KEY'])
427
+ self.analyzer = StockAnalyzer(self.alpaca)
428
+ self.data = self.analyzer.get_historical_data(self.analyzer.symbols)
429
+ self.auto_trade_log = [] # Store automatic trade actions
430
+
431
+ def display_charts(self):
432
+ # Dynamically adjust columns based on number of stocks and available width
433
+ symbols = list(self.data.keys())
434
+ symbol_to_name = self.analyzer.symbol_to_name
435
+ n = len(symbols)
436
+
437
+ # Calculate columns based on n for best fit
438
+ if n <= 3:
439
+ cols = n
440
+ elif n <= 6:
441
+ cols = 3
442
+ elif n <= 8:
443
+ cols = 4
444
+ elif n <= 12:
445
+ cols = 4
446
+ else:
447
+ cols = 5
448
+
449
+ rows = (n + cols - 1) // cols
450
+ subplot_titles = [
451
+ f"{symbol} - {symbol_to_name.get(symbol, '')}" for symbol in symbols
452
+ ]
453
+ fig = make_subplots(rows=rows, cols=cols, subplot_titles=subplot_titles)
454
+ for idx, symbol in enumerate(symbols):
455
+ df = self.data[symbol]
456
+ if not df.empty:
457
+ row = idx // cols + 1
458
+ col = idx % cols + 1
459
+ fig.add_trace(
460
+ go.Scatter(
461
+ x=df.index,
462
+ y=df['Close'],
463
+ mode='lines',
464
+ name=symbol,
465
+ hovertemplate=f"%{{x}}<br>{symbol}: %{{y:.2f}}<extra></extra>"
466
+ ),
467
+ row=row,
468
+ col=col
469
+ )
470
+ fig.update_layout(
471
+ title="Top Volume Stocks - Price Charts (Since 2023)",
472
+ height=max(400 * rows, 600),
473
+ showlegend=False,
474
+ dragmode=False,
475
+ )
476
+ # Enable scroll-zoom for each subplot (individual zoom)
477
+ fig.update_layout(
478
+ xaxis=dict(fixedrange=False),
479
+ yaxis=dict(fixedrange=False),
480
+ )
481
+ for i in range(1, rows * cols + 1):
482
+ fig.layout[f'xaxis{i}'].update(fixedrange=False)
483
+ fig.layout[f'yaxis{i}'].update(fixedrange=False)
484
+ st.plotly_chart(fig, use_container_width=True, config={"scrollZoom": True})
485
+
486
+ def manual_trade(self):
487
+ # Move all user inputs to the sidebar
488
+ with st.sidebar:
489
+ st.header("Manual Trade")
490
+ symbol = st.text_input('Enter stock symbol')
491
+ qty = int(st.number_input('Enter quantity'))
492
+ action = st.selectbox('Action', ['Buy', 'Sell'])
493
+ if st.button('Execute'):
494
+ if action == 'Buy':
495
+ order = self.alpaca.buy(symbol, qty)
496
+ else:
497
+ order = self.alpaca.sell(symbol, qty)
498
+ if order:
499
+ st.success(f"Order executed: {action} {qty} shares of {symbol}")
500
+ else:
501
+ st.error("Order failed")
502
+ st.header("Portfolio")
503
+ st.write("Cash Balance:")
504
+ st.write(self.alpaca.getCash())
505
+ st.write("Holdings:")
506
+ st.write(self.alpaca.getHoldings())
507
+ st.write("Recent Trades:")
508
+ st.write(pd.DataFrame(self.alpaca.trades))
509
+
510
+ def auto_trade_based_on_sentiment(self, sentiment):
511
+ # Add company name to each action
512
+ actions = []
513
+ symbol_to_name = self.analyzer.symbol_to_name
514
+ for symbol, sentiment_value in sentiment.items():
515
+ action = None
516
+ if sentiment_value == 'Positive':
517
+ order = self.alpaca.buy(symbol, 1)
518
+ action = 'Buy'
519
+ elif sentiment_value == 'Negative':
520
+ order = self.alpaca.sell(symbol, 1)
521
+ action = 'Sell'
522
+ else:
523
+ order = None
524
+ action = 'Hold'
525
+ actions.append({
526
+ 'symbol': symbol,
527
+ 'company_name': symbol_to_name.get(symbol, ''),
528
+ 'sentiment': sentiment_value,
529
+ 'action': action
530
+ })
531
+ self.auto_trade_log = actions
532
+ return actions
533
+
534
+ def background_auto_trade(app):
535
+ # This function runs in a background thread and does not require a TTY.
536
+ # The warning "tcgetpgrp failed: Not a tty" is harmless and can be ignored.
537
+ # It is likely caused by the environment in which the script is running (e.g., Streamlit, Docker, or a notebook).
538
+ # No code changes are needed for this warning.
539
+ while True:
540
+ sentiment = app.sentiment.get_news_sentiment(app.analyzer.symbols)
541
+ actions = []
542
+ for symbol, sentiment_value in sentiment.items():
543
+ action = None
544
+ if sentiment_value == 'Positive':
545
+ order = app.alpaca.buy(symbol, 1)
546
+ action = 'Buy'
547
+ elif sentiment_value == 'Negative':
548
+ order = app.alpaca.sell(symbol, 1)
549
+ action = 'Sell'
550
+ else:
551
+ order = None
552
+ action = 'Hold'
553
+ actions.append({
554
+ 'symbol': symbol,
555
+ 'sentiment': sentiment_value,
556
+ 'action': action
557
+ })
558
+ # Append to log file instead of overwriting
559
+ log_entry = {
560
+ "timestamp": datetime.now().isoformat(),
561
+ "actions": actions,
562
+ "sentiment": sentiment
563
+ }
564
+ try:
565
+ if os.path.exists(AUTO_TRADE_LOG_PATH):
566
+ with open(AUTO_TRADE_LOG_PATH, "r") as f:
567
+ log_data = json.load(f)
568
+ else:
569
+ log_data = []
570
+ except Exception:
571
+ log_data = []
572
+ log_data.append(log_entry)
573
+ with open(AUTO_TRADE_LOG_PATH, "w") as f:
574
+ json.dump(log_data, f)
575
+ time.sleep(AUTO_TRADE_INTERVAL)
576
+
577
+ def load_auto_trade_log():
578
+ try:
579
+ with open(AUTO_TRADE_LOG_PATH, "r") as f:
580
+ return json.load(f)
581
+ except Exception:
582
+ return None
583
+
584
+ class TradingApp:
585
+ def __init__(self):
586
+ self.alpaca = AlpacaTrader(st.secrets['ALPACA_API_KEY'], st.secrets['ALPACA_SECRET_KEY'], 'https://paper-api.alpaca.markets')
587
+ self.sentiment = NewsSentiment(st.secrets['NEWS_API_KEY'])
588
+ self.analyzer = StockAnalyzer(self.alpaca)
589
+ self.data = self.analyzer.get_historical_data(self.analyzer.symbols)
590
+ self.auto_trade_log = [] # Store automatic trade actions
591
+
592
+ def display_charts(self):
593
+ # Dynamically adjust columns based on number of stocks and available width
594
+ symbols = list(self.data.keys())
595
+ symbol_to_name = self.analyzer.symbol_to_name
596
+ n = len(symbols)
597
+
598
+ # Calculate columns based on n for best fit
599
+ if n <= 3:
600
+ cols = n
601
+ elif n <= 6:
602
+ cols = 3
603
+ elif n <= 8:
604
+ cols = 4
605
+ elif n <= 12:
606
+ cols = 4
607
+ else:
608
+ cols = 5
609
+
610
+ rows = (n + cols - 1) // cols
611
+ subplot_titles = [
612
+ f"{symbol} - {symbol_to_name.get(symbol, '')}" for symbol in symbols
613
+ ]
614
+ fig = make_subplots(rows=rows, cols=cols, subplot_titles=subplot_titles)
615
+ for idx, symbol in enumerate(symbols):
616
+ df = self.data[symbol]
617
+ if not df.empty:
618
+ row = idx // cols + 1
619
+ col = idx % cols + 1
620
+ fig.add_trace(
621
+ go.Scatter(
622
+ x=df.index,
623
+ y=df['Close'],
624
+ mode='lines',
625
+ name=symbol,
626
+ hovertemplate=f"%{{x}}<br>{symbol}: %{{y:.2f}}<extra></extra>"
627
+ ),
628
+ row=row,
629
+ col=col
630
+ )
631
+ fig.update_layout(
632
+ title="Top Volume Stocks - Price Charts (Since 2023)",
633
+ height=max(400 * rows, 600),
634
+ showlegend=False,
635
+ dragmode=False,
636
+ )
637
+ # Enable scroll-zoom for each subplot (individual zoom)
638
+ fig.update_layout(
639
+ xaxis=dict(fixedrange=False),
640
+ yaxis=dict(fixedrange=False),
641
+ )
642
+ for i in range(1, rows * cols + 1):
643
+ fig.layout[f'xaxis{i}'].update(fixedrange=False)
644
+ fig.layout[f'yaxis{i}'].update(fixedrange=False)
645
+ st.plotly_chart(fig, use_container_width=True, config={"scrollZoom": True})
646
+
647
+ def manual_trade(self):
648
+ # Move all user inputs to the sidebar
649
+ with st.sidebar:
650
+ st.header("Manual Trade")
651
+ symbol = st.text_input('Enter stock symbol')
652
+ qty = int(st.number_input('Enter quantity'))
653
+ action = st.selectbox('Action', ['Buy', 'Sell'])
654
+ if st.button('Execute'):
655
+ if action == 'Buy':
656
+ order = self.alpaca.buy(symbol, qty)
657
+ else:
658
+ order = self.alpaca.sell(symbol, qty)
659
+ if order:
660
+ st.success(f"Order executed: {action} {qty} shares of {symbol}")
661
+ else:
662
+ st.error("Order failed")
663
+ st.header("Portfolio")
664
+ st.write("Cash Balance:")
665
+ st.write(self.alpaca.getCash())
666
+ st.write("Holdings:")
667
+ st.write(self.alpaca.getHoldings())
668
+ st.write("Recent Trades:")
669
+ st.write(pd.DataFrame(self.alpaca.trades))
670
+
671
+ def auto_trade_based_on_sentiment(self, sentiment):
672
+ # Add company name to each action
673
+ actions = []
674
+ symbol_to_name = self.analyzer.symbol_to_name
675
+ for symbol, sentiment_value in sentiment.items():
676
+ action = None
677
+ if sentiment_value == 'Positive':
678
+ order = self.alpaca.buy(symbol, 1)
679
+ action = 'Buy'
680
+ elif sentiment_value == 'Negative':
681
+ order = self.alpaca.sell(symbol, 1)
682
+ action = 'Sell'
683
+ else:
684
+ order = None
685
+ action = 'Hold'
686
+ actions.append({
687
+ 'symbol': symbol,
688
+ 'company_name': symbol_to_name.get(symbol, ''),
689
+ 'sentiment': sentiment_value,
690
+ 'action': action
691
+ })
692
+ self.auto_trade_log = actions
693
+ return actions
694
+
695
+ def background_auto_trade(app):
696
+ # This function runs in a background thread and does not require a TTY.
697
+ # The warning "tcgetpgrp failed: Not a tty" is harmless and can be ignored.
698
+ # It is likely caused by the environment in which the script is running (e.g., Streamlit, Docker, or a notebook).
699
+ # No code changes are needed for this warning.
700
+ while True:
701
+ sentiment = app.sentiment.get_news_sentiment(app.analyzer.symbols)
702
+ actions = []
703
+ for symbol, sentiment_value in sentiment.items():
704
+ action = None
705
+ if sentiment_value == 'Positive':
706
+ order = app.alpaca.buy(symbol, 1)
707
+ action = 'Buy'
708
+ elif sentiment_value == 'Negative':
709
+ order = app.alpaca.sell(symbol, 1)
710
+ action = 'Sell'
711
+ else:
712
+ order = None
713
+ action = 'Hold'
714
+ actions.append({
715
+ 'symbol': symbol,
716
+ 'sentiment': sentiment_value,
717
+ 'action': action
718
+ })
719
+ # Append to log file instead of overwriting
720
+ log_entry = {
721
+ "timestamp": datetime.now().isoformat(),
722
+ "actions": actions,
723
+ "sentiment": sentiment
724
+ }
725
+ try:
726
+ if os.path.exists(AUTO_TRADE_LOG_PATH):
727
+ with open(AUTO_TRADE_LOG_PATH, "r") as f:
728
+ log_data = json.load(f)
729
+ else:
730
+ log_data = []
731
+ except Exception:
732
+ log_data = []
733
+ log_data.append(log_entry)
734
+ with open(AUTO_TRADE_LOG_PATH, "w") as f:
735
+ json.dump(log_data, f)
736
+ time.sleep(AUTO_TRADE_INTERVAL)
737
+
738
+ def load_auto_trade_log():
739
+ try:
740
+ with open(AUTO_TRADE_LOG_PATH, "r") as f:
741
+ return json.load(f)
742
+ except Exception:
743
+ return None
744
+
745
+ # Remove the following line, as st.query_params is not callable and causes a TypeError
746
+ # st.query_params() # This is a no-op but ensures Streamlit doesn't rerun due to query params
747
 
748
  class TradingApp:
749
  def __init__(self):