Spaces:
Running
Running
Faham commited on
Commit Β·
8e6c67f
1
Parent(s): 6da16d5
UPDATE: fetch tickers from yfinanc
Browse files- Home.py +164 -17
- resource_monitor.py +1 -0
- terminal_client.py +106 -15
Home.py
CHANGED
|
@@ -64,6 +64,103 @@ client = OpenAI(
|
|
| 64 |
discovered_tools = []
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
async def get_news_data(ticker: str) -> str:
|
| 68 |
"""Get news data by calling the news server via MCP."""
|
| 69 |
try:
|
|
@@ -385,10 +482,10 @@ def create_stock_chart(ticker: str):
|
|
| 385 |
- **Expected Change:** ${price_change:.2f} ({price_change_pct:+.2f}%)
|
| 386 |
- **Confidence Range:** ${confidence_lower:.2f} - ${confidence_upper:.2f} (Β±${confidence_range/2:.2f})
|
| 387 |
- **Model Training Time:** {training_time:.2f}s
|
| 388 |
-
|
| 389 |
-
β οΈ **Disclaimer**: Stock predictions have approximately 51% accuracy.
|
| 390 |
-
These forecasts are for informational purposes only and should not be used as
|
| 391 |
-
the sole basis for investment decisions. Always conduct your own research
|
| 392 |
and consider consulting with financial advisors.
|
| 393 |
"""
|
| 394 |
)
|
|
@@ -765,23 +862,73 @@ def main():
|
|
| 765 |
st.session_state.servers_tested = True
|
| 766 |
|
| 767 |
# Available tickers
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
"TSLA": "Tesla Inc.",
|
| 771 |
-
"MSFT": "Microsoft Corporation",
|
| 772 |
-
"GOOG": "Alphabet Inc. (Google)",
|
| 773 |
-
}
|
| 774 |
|
| 775 |
# Sidebar for ticker selection
|
| 776 |
st.sidebar.header("π Stock Selection")
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
placeholder="
|
|
|
|
| 783 |
)
|
| 784 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
# Clear cache when ticker changes
|
| 786 |
if (
|
| 787 |
"current_ticker" in st.session_state
|
|
@@ -808,7 +955,7 @@ def main():
|
|
| 808 |
2. View the interactive stock price chart
|
| 809 |
3. Ask questions about the stock's performance, news, or investment advice
|
| 810 |
4. The agent will fetch real-time data and provide comprehensive analysis
|
| 811 |
-
|
| 812 |
**Example questions:**
|
| 813 |
- "How is this stock performing?"
|
| 814 |
- "What's the latest news about this company?"
|
|
|
|
| 64 |
discovered_tools = []
|
| 65 |
|
| 66 |
|
| 67 |
+
@st.cache_data(ttl=3600) # Cache for 1 hour
|
| 68 |
+
def get_available_tickers():
|
| 69 |
+
"""Fetch available tickers from yfinance using the Lookup class."""
|
| 70 |
+
try:
|
| 71 |
+
# Use yfinance Lookup to get all available stocks
|
| 72 |
+
print("Fetching all available stock tickers from yfinance...")
|
| 73 |
+
|
| 74 |
+
# Create a lookup for stocks
|
| 75 |
+
lookup = yf.Lookup("stock")
|
| 76 |
+
|
| 77 |
+
# Get stock results
|
| 78 |
+
stock_results = lookup.get_stock(count=2000) # Get up to 500 stocks
|
| 79 |
+
|
| 80 |
+
if stock_results is not None and not stock_results.empty:
|
| 81 |
+
print(f"Found {len(stock_results)} stock tickers from yfinance")
|
| 82 |
+
|
| 83 |
+
# Convert to dictionary format - use index as ticker symbol
|
| 84 |
+
tickers_dict = {}
|
| 85 |
+
for index_val, row in stock_results.iterrows():
|
| 86 |
+
# Use the index as the ticker symbol
|
| 87 |
+
ticker = str(index_val)
|
| 88 |
+
|
| 89 |
+
# Get company name from shortName column
|
| 90 |
+
name = row.get("shortName", ticker)
|
| 91 |
+
|
| 92 |
+
if ticker and name and ticker != "nan":
|
| 93 |
+
tickers_dict[ticker] = name
|
| 94 |
+
|
| 95 |
+
print(f"Successfully loaded {len(tickers_dict)} valid tickers")
|
| 96 |
+
return tickers_dict
|
| 97 |
+
else:
|
| 98 |
+
print("No stock results found, using fallback list")
|
| 99 |
+
|
| 100 |
+
except Exception as e:
|
| 101 |
+
print(f"Error fetching tickers from yfinance Lookup: {e}")
|
| 102 |
+
|
| 103 |
+
# Fallback to 10 most popular tickers if Lookup fails
|
| 104 |
+
try:
|
| 105 |
+
print("Using fallback to 10 most popular tickers...")
|
| 106 |
+
popular_tickers = {}
|
| 107 |
+
|
| 108 |
+
# 10 most popular tickers
|
| 109 |
+
popular_ticker_list = [
|
| 110 |
+
"AAPL",
|
| 111 |
+
"MSFT",
|
| 112 |
+
"GOOGL",
|
| 113 |
+
"AMZN",
|
| 114 |
+
"TSLA",
|
| 115 |
+
"META",
|
| 116 |
+
"NVDA",
|
| 117 |
+
"BRK-B",
|
| 118 |
+
"JNJ",
|
| 119 |
+
"JPM",
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
print(f"Loading {len(popular_ticker_list)} popular tickers...")
|
| 123 |
+
|
| 124 |
+
# Get company names for each ticker
|
| 125 |
+
for ticker in popular_ticker_list:
|
| 126 |
+
try:
|
| 127 |
+
ticker_obj = yf.Ticker(ticker)
|
| 128 |
+
info = ticker_obj.info
|
| 129 |
+
|
| 130 |
+
if info and (info.get("longName") or info.get("shortName")):
|
| 131 |
+
company_name = info.get("longName", info.get("shortName", ticker))
|
| 132 |
+
popular_tickers[ticker] = company_name
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
# Skip tickers that cause errors
|
| 136 |
+
continue
|
| 137 |
+
|
| 138 |
+
print(f"Successfully loaded {len(popular_tickers)} tickers")
|
| 139 |
+
return popular_tickers
|
| 140 |
+
|
| 141 |
+
except Exception as e:
|
| 142 |
+
st.error(f"Error fetching available tickers: {e}")
|
| 143 |
+
# Final fallback to basic tickers if there's an error
|
| 144 |
+
return {
|
| 145 |
+
"AAPL": "Apple Inc.",
|
| 146 |
+
"TSLA": "Tesla Inc.",
|
| 147 |
+
"MSFT": "Microsoft Corporation",
|
| 148 |
+
"GOOGL": "Alphabet Inc. (Google)",
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@st.cache_data(ttl=3600) # Cache for 1 hour
|
| 153 |
+
def search_ticker(ticker_symbol):
|
| 154 |
+
"""Search for a ticker symbol and get its company name using yfinance."""
|
| 155 |
+
try:
|
| 156 |
+
ticker = yf.Ticker(ticker_symbol)
|
| 157 |
+
info = ticker.info
|
| 158 |
+
company_name = info.get("longName", info.get("shortName", ticker_symbol))
|
| 159 |
+
return company_name
|
| 160 |
+
except Exception as e:
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
|
| 164 |
async def get_news_data(ticker: str) -> str:
|
| 165 |
"""Get news data by calling the news server via MCP."""
|
| 166 |
try:
|
|
|
|
| 482 |
- **Expected Change:** ${price_change:.2f} ({price_change_pct:+.2f}%)
|
| 483 |
- **Confidence Range:** ${confidence_lower:.2f} - ${confidence_upper:.2f} (Β±${confidence_range/2:.2f})
|
| 484 |
- **Model Training Time:** {training_time:.2f}s
|
| 485 |
+
|
| 486 |
+
β οΈ **Disclaimer**: Stock predictions have approximately 51% accuracy.
|
| 487 |
+
These forecasts are for informational purposes only and should not be used as
|
| 488 |
+
the sole basis for investment decisions. Always conduct your own research
|
| 489 |
and consider consulting with financial advisors.
|
| 490 |
"""
|
| 491 |
)
|
|
|
|
| 862 |
st.session_state.servers_tested = True
|
| 863 |
|
| 864 |
# Available tickers
|
| 865 |
+
with st.spinner("π Loading available tickers..."):
|
| 866 |
+
available_tickers = get_available_tickers()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
|
| 868 |
# Sidebar for ticker selection
|
| 869 |
st.sidebar.header("π Stock Selection")
|
| 870 |
+
|
| 871 |
+
# Add search functionality
|
| 872 |
+
st.sidebar.subheader("π Search Custom Ticker")
|
| 873 |
+
custom_ticker = st.sidebar.text_input(
|
| 874 |
+
"Enter ticker symbol (e.g., AAPL, TSLA):",
|
| 875 |
+
placeholder="Enter ticker symbol...",
|
| 876 |
+
key="custom_ticker_input",
|
| 877 |
)
|
| 878 |
|
| 879 |
+
# Add info button with helpful information
|
| 880 |
+
with st.sidebar.expander("βΉοΈ Can't find your ticker in the list?", expanded=False):
|
| 881 |
+
st.markdown(
|
| 882 |
+
"""
|
| 883 |
+
**Can't find your ticker in the list?**
|
| 884 |
+
|
| 885 |
+
Use this search box to check if a ticker is available:
|
| 886 |
+
|
| 887 |
+
β
**How it works:**
|
| 888 |
+
- Enter any ticker symbol (e.g., AAPL, TSLA, GOOGL)
|
| 889 |
+
- If found, it will be automatically added to the dropdown
|
| 890 |
+
- You can then select it from the "Popular Stocks" list below
|
| 891 |
+
|
| 892 |
+
β
**Examples:**
|
| 893 |
+
- `AAPL` β Apple Inc.
|
| 894 |
+
- `TSLA` β Tesla Inc.
|
| 895 |
+
- `MSFT` β Microsoft Corporation
|
| 896 |
+
- `GOOGL` β Alphabet Inc.
|
| 897 |
+
|
| 898 |
+
β
**Tips:**
|
| 899 |
+
- Use uppercase letters for best results
|
| 900 |
+
- Most major US and international stocks are supported
|
| 901 |
+
- If not found, the ticker might not be available on Yahoo Finance
|
| 902 |
+
"""
|
| 903 |
+
)
|
| 904 |
+
|
| 905 |
+
if custom_ticker:
|
| 906 |
+
custom_ticker = custom_ticker.upper().strip()
|
| 907 |
+
if custom_ticker:
|
| 908 |
+
# Search for the custom ticker
|
| 909 |
+
company_name = search_ticker(custom_ticker)
|
| 910 |
+
if company_name:
|
| 911 |
+
st.sidebar.success(f"β
Found: {custom_ticker} - {company_name}")
|
| 912 |
+
# Add to available tickers temporarily
|
| 913 |
+
available_tickers[custom_ticker] = company_name
|
| 914 |
+
else:
|
| 915 |
+
st.sidebar.error(f"β Could not find ticker: {custom_ticker}")
|
| 916 |
+
|
| 917 |
+
st.sidebar.subheader("π Popular Stocks")
|
| 918 |
+
|
| 919 |
+
# Only show selectbox if tickers are loaded
|
| 920 |
+
if available_tickers and len(available_tickers) > 0:
|
| 921 |
+
selected_ticker = st.sidebar.selectbox(
|
| 922 |
+
"Choose a stock ticker:",
|
| 923 |
+
options=list(available_tickers.keys()),
|
| 924 |
+
format_func=lambda x: f"{x} - {available_tickers[x]}",
|
| 925 |
+
index=None,
|
| 926 |
+
placeholder="Select a ticker...",
|
| 927 |
+
)
|
| 928 |
+
else:
|
| 929 |
+
st.sidebar.error("β Failed to load tickers. Please refresh the page.")
|
| 930 |
+
selected_ticker = None
|
| 931 |
+
|
| 932 |
# Clear cache when ticker changes
|
| 933 |
if (
|
| 934 |
"current_ticker" in st.session_state
|
|
|
|
| 955 |
2. View the interactive stock price chart
|
| 956 |
3. Ask questions about the stock's performance, news, or investment advice
|
| 957 |
4. The agent will fetch real-time data and provide comprehensive analysis
|
| 958 |
+
|
| 959 |
**Example questions:**
|
| 960 |
- "How is this stock performing?"
|
| 961 |
- "What's the latest news about this company?"
|
resource_monitor.py
CHANGED
|
@@ -4,6 +4,7 @@ import threading
|
|
| 4 |
import plotly.graph_objects as go
|
| 5 |
from datetime import datetime
|
| 6 |
import json
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
class ResourceMonitor:
|
|
|
|
| 4 |
import plotly.graph_objects as go
|
| 5 |
from datetime import datetime
|
| 6 |
import json
|
| 7 |
+
from typing import Dict
|
| 8 |
|
| 9 |
|
| 10 |
class ResourceMonitor:
|
terminal_client.py
CHANGED
|
@@ -6,6 +6,7 @@ from openai import OpenAI
|
|
| 6 |
from mcp.client.session import ClientSession
|
| 7 |
from mcp.client.stdio import stdio_client
|
| 8 |
from mcp import StdioServerParameters, types
|
|
|
|
| 9 |
|
| 10 |
# Load API key from.env file
|
| 11 |
load_dotenv()
|
|
@@ -30,6 +31,69 @@ client = OpenAI(
|
|
| 30 |
discovered_tools = []
|
| 31 |
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
async def get_news_data(ticker: str) -> str:
|
| 34 |
"""Get news data by calling the news server via MCP."""
|
| 35 |
try:
|
|
@@ -296,28 +360,28 @@ async def main():
|
|
| 296 |
# Initialize tools
|
| 297 |
initialize_tools()
|
| 298 |
|
| 299 |
-
#
|
| 300 |
-
available_tickers =
|
| 301 |
|
| 302 |
print("=== QueryStockAI ===")
|
| 303 |
print("Select a stock ticker to analyze:")
|
| 304 |
-
print("1. AAPL (Apple)")
|
| 305 |
-
print("2. TSLA (Tesla)")
|
| 306 |
-
print("3. MSFT (Microsoft)")
|
| 307 |
-
print("4. GOOG (Google)")
|
| 308 |
print("Type 'quit' or 'exit' to stop the program.")
|
| 309 |
print("=" * 50)
|
| 310 |
|
| 311 |
while True:
|
| 312 |
# Show ticker menu
|
| 313 |
print("\nπ Available Stocks:")
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
| 316 |
print(" q. Quit")
|
| 317 |
|
| 318 |
# Get user selection
|
| 319 |
selection = (
|
| 320 |
-
input("\n㪠Select a stock (
|
|
|
|
|
|
|
| 321 |
)
|
| 322 |
|
| 323 |
# Check if user wants to exit
|
|
@@ -325,13 +389,40 @@ async def main():
|
|
| 325 |
print("π Goodbye!")
|
| 326 |
break
|
| 327 |
|
| 328 |
-
#
|
| 329 |
-
if selection
|
| 330 |
-
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
-
# Get the selected ticker
|
| 334 |
-
selected_ticker = available_tickers[selection]
|
| 335 |
print(f"\nπ Selected: {selected_ticker}")
|
| 336 |
|
| 337 |
# Always fetch both news and stock data by default
|
|
|
|
| 6 |
from mcp.client.session import ClientSession
|
| 7 |
from mcp.client.stdio import stdio_client
|
| 8 |
from mcp import StdioServerParameters, types
|
| 9 |
+
import yfinance as yf
|
| 10 |
|
| 11 |
# Load API key from.env file
|
| 12 |
load_dotenv()
|
|
|
|
| 31 |
discovered_tools = []
|
| 32 |
|
| 33 |
|
| 34 |
+
def get_available_tickers():
|
| 35 |
+
"""Hardcoded tickers for testing"""
|
| 36 |
+
|
| 37 |
+
# Fallback to 10 most popular tickers if Lookup fails
|
| 38 |
+
try:
|
| 39 |
+
print("Using fallback to 10 most popular tickers...")
|
| 40 |
+
popular_tickers = {}
|
| 41 |
+
|
| 42 |
+
# 10 most popular tickers
|
| 43 |
+
popular_ticker_list = [
|
| 44 |
+
"AAPL",
|
| 45 |
+
"MSFT",
|
| 46 |
+
"GOOGL",
|
| 47 |
+
"AMZN",
|
| 48 |
+
"TSLA",
|
| 49 |
+
"META",
|
| 50 |
+
"NVDA",
|
| 51 |
+
"BRK-B",
|
| 52 |
+
"JNJ",
|
| 53 |
+
"JPM",
|
| 54 |
+
]
|
| 55 |
+
|
| 56 |
+
print(f"Loading {len(popular_ticker_list)} popular tickers...")
|
| 57 |
+
|
| 58 |
+
# Get company names for each ticker
|
| 59 |
+
for ticker in popular_ticker_list:
|
| 60 |
+
try:
|
| 61 |
+
ticker_obj = yf.Ticker(ticker)
|
| 62 |
+
info = ticker_obj.info
|
| 63 |
+
|
| 64 |
+
if info and (info.get("longName") or info.get("shortName")):
|
| 65 |
+
company_name = info.get("longName", info.get("shortName", ticker))
|
| 66 |
+
popular_tickers[ticker] = company_name
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
# Skip tickers that cause errors
|
| 70 |
+
continue
|
| 71 |
+
|
| 72 |
+
print(f"Successfully loaded {len(popular_tickers)} tickers")
|
| 73 |
+
return popular_tickers
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f"Error fetching available tickers: {e}")
|
| 77 |
+
# Final fallback to basic tickers if there's an error
|
| 78 |
+
return {
|
| 79 |
+
"AAPL": "Apple Inc.",
|
| 80 |
+
"TSLA": "Tesla Inc.",
|
| 81 |
+
"MSFT": "Microsoft Corporation",
|
| 82 |
+
"GOOGL": "Alphabet Inc. (Google)",
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def search_ticker(ticker_symbol):
|
| 87 |
+
"""Search for a ticker symbol and get its company name using yfinance."""
|
| 88 |
+
try:
|
| 89 |
+
ticker = yf.Ticker(ticker_symbol)
|
| 90 |
+
info = ticker.info
|
| 91 |
+
company_name = info.get("longName", info.get("shortName", ticker_symbol))
|
| 92 |
+
return company_name
|
| 93 |
+
except Exception as e:
|
| 94 |
+
return None
|
| 95 |
+
|
| 96 |
+
|
| 97 |
async def get_news_data(ticker: str) -> str:
|
| 98 |
"""Get news data by calling the news server via MCP."""
|
| 99 |
try:
|
|
|
|
| 360 |
# Initialize tools
|
| 361 |
initialize_tools()
|
| 362 |
|
| 363 |
+
# Get available tickers
|
| 364 |
+
available_tickers = get_available_tickers()
|
| 365 |
|
| 366 |
print("=== QueryStockAI ===")
|
| 367 |
print("Select a stock ticker to analyze:")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
print("Type 'quit' or 'exit' to stop the program.")
|
| 369 |
print("=" * 50)
|
| 370 |
|
| 371 |
while True:
|
| 372 |
# Show ticker menu
|
| 373 |
print("\nπ Available Stocks:")
|
| 374 |
+
ticker_list = list(available_tickers.items())
|
| 375 |
+
for i, (ticker, name) in enumerate(ticker_list, 1):
|
| 376 |
+
print(f" {i}. {ticker} ({name})")
|
| 377 |
+
print(" s. Search for custom ticker")
|
| 378 |
print(" q. Quit")
|
| 379 |
|
| 380 |
# Get user selection
|
| 381 |
selection = (
|
| 382 |
+
input("\n㪠Select a stock, search (s), or type 'quit': ")
|
| 383 |
+
.strip()
|
| 384 |
+
.lower()
|
| 385 |
)
|
| 386 |
|
| 387 |
# Check if user wants to exit
|
|
|
|
| 389 |
print("π Goodbye!")
|
| 390 |
break
|
| 391 |
|
| 392 |
+
# Handle search option
|
| 393 |
+
if selection == "s":
|
| 394 |
+
custom_ticker = (
|
| 395 |
+
input("Enter ticker symbol (e.g., AAPL): ").strip().upper()
|
| 396 |
+
)
|
| 397 |
+
if custom_ticker:
|
| 398 |
+
company_name = search_ticker(custom_ticker)
|
| 399 |
+
if company_name:
|
| 400 |
+
print(f"β
Found: {custom_ticker} - {company_name}")
|
| 401 |
+
# Add to available tickers temporarily
|
| 402 |
+
available_tickers[custom_ticker] = company_name
|
| 403 |
+
selected_ticker = custom_ticker
|
| 404 |
+
else:
|
| 405 |
+
print(f"β Could not find ticker: {custom_ticker}")
|
| 406 |
+
continue
|
| 407 |
+
else:
|
| 408 |
+
print("β Please enter a valid ticker symbol.")
|
| 409 |
+
continue
|
| 410 |
+
else:
|
| 411 |
+
# Check if selection is valid
|
| 412 |
+
try:
|
| 413 |
+
selection_num = int(selection)
|
| 414 |
+
if selection_num < 1 or selection_num > len(ticker_list):
|
| 415 |
+
print(
|
| 416 |
+
f"β Invalid selection. Please choose 1-{len(ticker_list)}, 's' for search, or 'quit'."
|
| 417 |
+
)
|
| 418 |
+
continue
|
| 419 |
+
selected_ticker = ticker_list[selection_num - 1][0]
|
| 420 |
+
except ValueError:
|
| 421 |
+
print(
|
| 422 |
+
"β Invalid selection. Please enter a number, 's' for search, or 'quit'."
|
| 423 |
+
)
|
| 424 |
+
continue
|
| 425 |
|
|
|
|
|
|
|
| 426 |
print(f"\nπ Selected: {selected_ticker}")
|
| 427 |
|
| 428 |
# Always fetch both news and stock data by default
|