Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.io as pio | |
| import os | |
| # Update the default color sequence for the plotly_dark template to green. | |
| pio.templates["plotly_dark"].layout.colorway = ["#00cc44"] | |
| # Global API key and constant limit (backend only) | |
| API_KEY = os.getenv("FMP_API_KEY") | |
| BASE_URL = "https://financialmodelingprep.com/api/v3/stock-screener" | |
| LIMIT = 1000 | |
| # Set wide layout and page title | |
| st.set_page_config(layout="wide", page_title="Stock Screener") | |
| # App explanation in main area | |
| st.title("Stock Screener") | |
| st.write( | |
| "A research tool for filtering stocks by price, volume, market cap, sector, and other fundamentals. " | |
| "Useful for identifying sets of securities based on specific criteria." | |
| ) | |
| st.info( | |
| "Use the sidebar to set filters. Click **Run Analysis** to retrieve results and display charts. " | |
| "All filters are optional. If no results appear, try loosening the constraints." | |
| ) | |
| # Sidebar inputs | |
| st.sidebar.header("Filters") | |
| # Numeric Filters | |
| with st.sidebar.expander("Numeric Filters", expanded=True): | |
| market_cap_min = st.number_input( | |
| "Market Cap More Than", | |
| value=1000000000, | |
| help="Minimum market capitalization." | |
| ) | |
| market_cap_max = st.number_input( | |
| "Market Cap Lower Than", | |
| value=50000000000, | |
| help="Maximum market capitalization." | |
| ) | |
| price_min = st.number_input( | |
| "Price More Than", | |
| value=10.0, | |
| help="Minimum stock price." | |
| ) | |
| price_max = st.number_input( | |
| "Price Lower Than", | |
| value=500.0, | |
| help="Maximum stock price." | |
| ) | |
| beta_min = st.number_input( | |
| "Beta More Than", | |
| value=0.5, | |
| help="Minimum beta value." | |
| ) | |
| beta_max = st.number_input( | |
| "Beta Lower Than", | |
| value=2.0, | |
| help="Maximum beta value." | |
| ) | |
| volume_min = st.number_input( | |
| "Volume More Than", | |
| value=10000, | |
| help="Minimum trading volume." | |
| ) | |
| volume_max = st.number_input( | |
| "Volume Lower Than", | |
| value=1000000, | |
| help="Maximum trading volume." | |
| ) | |
| dividend_min = st.number_input( | |
| "Dividend More Than", | |
| value=0.0, | |
| help="Minimum dividend value." | |
| ) | |
| dividend_max = st.number_input( | |
| "Dividend Lower Than", | |
| value=5.0, | |
| help="Maximum dividend value." | |
| ) | |
| # Categorical Filters | |
| with st.sidebar.expander("Categorical Filters", expanded=True): | |
| sector_options = [ | |
| "Technology", "Financial Services", "Consumer Cyclical", "Energy", "Industrials", | |
| "Basic Materials", "Communication Services", "Consumer Defensive", "Healthcare", | |
| "Real Estate", "Utilities", "Industrial Goods", "Financial", "Services", "Conglomerates" | |
| ] | |
| selected_sectors = st.multiselect( | |
| "Sector", | |
| options=sector_options, | |
| help="Select one or more sectors." | |
| ) | |
| industry_options = [ | |
| "Autos", "Banks", "Banks Diversified", "Software", "Banks Regional", | |
| "Beverages Alcoholic", "Beverages Brewers", "Beverages Non-Alcoholic" | |
| ] | |
| selected_industries = st.multiselect( | |
| "Industry", | |
| options=industry_options, | |
| help="Select one or more industries." | |
| ) | |
| country_options = ["US", "UK", "MX", "BR", "RU", "HK", "CA"] | |
| selected_countries = st.multiselect( | |
| "Country", | |
| options=country_options, | |
| help="Select one or more countries." | |
| ) | |
| exchange_options = ["nyse", "nasdaq", "amex", "euronext", "tsx", "etf", "mutual_fund"] | |
| selected_exchanges = st.multiselect( | |
| "Exchange", | |
| options=exchange_options, | |
| help="Select one or more exchanges." | |
| ) | |
| # Boolean Filters | |
| with st.sidebar.expander("Boolean Filters", expanded=True): | |
| is_etf = st.checkbox( | |
| "Is ETF", | |
| value=False, | |
| help="Check to return only ETFs." | |
| ) | |
| is_fund = st.checkbox( | |
| "Is Fund", | |
| value=False, | |
| help="Check to return only funds." | |
| ) | |
| is_actively_trading = st.checkbox( | |
| "Is Actively Trading", | |
| value=True, | |
| help="Check to return only actively traded stocks." | |
| ) | |
| # Run Analysis button (placed outside expanders) | |
| run_analysis = st.sidebar.button("Run Analysis") | |
| def get_stock_data(params): | |
| # Copy parameters and add API key. | |
| filters = params.copy() | |
| filters["apikey"] = API_KEY | |
| # Convert list values to comma-separated strings. | |
| for key, value in filters.items(): | |
| if isinstance(value, list) and value: | |
| filters[key] = ",".join(map(str, value)) | |
| try: | |
| response = requests.get(BASE_URL, params=filters, timeout=10) | |
| response.raise_for_status() | |
| data = response.json() | |
| if not data: | |
| st.error("No results found for the provided filters.") | |
| return pd.DataFrame() | |
| return pd.DataFrame(data) | |
| except Exception: | |
| st.error("An error occurred while fetching data.") | |
| return pd.DataFrame() | |
| if run_analysis: | |
| # Build parameter dictionary. | |
| params = {} | |
| params["marketCapMoreThan"] = market_cap_min | |
| params["marketCapLowerThan"] = market_cap_max | |
| params["priceMoreThan"] = price_min | |
| params["priceLowerThan"] = price_max | |
| params["betaMoreThan"] = beta_min | |
| params["betaLowerThan"] = beta_max | |
| params["volumeMoreThan"] = volume_min | |
| params["volumeLowerThan"] = volume_max | |
| params["dividendMoreThan"] = dividend_min | |
| params["dividendLowerThan"] = dividend_max | |
| if selected_sectors: | |
| params["sector"] = selected_sectors | |
| if selected_industries: | |
| params["industry"] = selected_industries | |
| if selected_countries: | |
| params["country"] = selected_countries | |
| if selected_exchanges: | |
| params["exchange"] = selected_exchanges | |
| params["isEtf"] = is_etf | |
| params["isFund"] = is_fund | |
| params["isActivelyTrading"] = is_actively_trading | |
| # Set limit in the backend. | |
| params["limit"] = LIMIT | |
| with st.spinner("Fetching stock data..."): | |
| df = get_stock_data(params) | |
| if not df.empty: | |
| st.success("Data fetched successfully!") | |
| #with st.expander("Results", expanded=True): | |
| with st.container(border=True): | |
| # Display the data table. | |
| with st.container(border=True): | |
| st.dataframe(df) | |
| # Common hover data for scatter plots. | |
| hover_fields = ["symbol", "companyName", "sector", "industry", "exchangeShortName", "country"] | |
| # Chart 1: Price vs Market Cap (Scatter) | |
| with st.container(border=True): | |
| try: | |
| fig1 = px.scatter( | |
| df, | |
| x="price", | |
| y="marketCap", | |
| size="volume", | |
| title="Price vs Market Cap", | |
| hover_data=hover_fields, | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig1, use_container_width=True) | |
| except Exception: | |
| st.warning("Scatter plot could not be generated.") | |
| # Chart 2: Sector Distribution (Bar Chart) | |
| with st.container(border=True): | |
| try: | |
| sector_counts = df["sector"].value_counts().reset_index() | |
| sector_counts.columns = ["Sector", "Count"] | |
| fig2 = px.bar( | |
| sector_counts, | |
| x="Sector", | |
| y="Count", | |
| title="Sector Distribution", | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig2, use_container_width=True) | |
| except Exception: | |
| st.warning("Sector distribution chart could not be generated.") | |
| # Chart 3: Price Distribution (Histogram) | |
| with st.container(border=True): | |
| try: | |
| fig3 = px.histogram( | |
| df, | |
| x="price", | |
| nbins=30, | |
| title="Price Distribution", | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig3, use_container_width=True) | |
| except Exception: | |
| st.warning("Price distribution chart could not be generated.") | |
| # Chart 4: Market Cap vs Volume (Scatter) | |
| with st.container(border=True): | |
| try: | |
| fig4 = px.scatter( | |
| df, | |
| x="volume", | |
| y="marketCap", | |
| size="price", | |
| title="Market Cap vs Volume", | |
| hover_data=hover_fields, | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig4, use_container_width=True) | |
| except Exception: | |
| st.warning("Market Cap vs Volume chart could not be generated.") | |
| # Chart 5: Country Breakdown (Bar Chart) | |
| with st.container(border=True): | |
| try: | |
| country_counts = df["country"].value_counts().reset_index() | |
| country_counts.columns = ["Country", "Count"] | |
| fig5 = px.bar( | |
| country_counts, | |
| x="Country", | |
| y="Count", | |
| title="Country Breakdown", | |
| template="plotly_dark", | |
| #color_discrete_sequence=px.colors.qualitative.Plotly # override global green | |
| ) | |
| st.plotly_chart(fig5, use_container_width=True) | |
| except Exception: | |
| st.warning("Country breakdown chart could not be generated.") | |
| # Chart 6: Dividend vs Price (Scatter) | |
| with st.container(border=True): | |
| try: | |
| if "lastAnnualDividend" in df.columns: | |
| fig6 = px.scatter( | |
| df, | |
| x="price", | |
| y="lastAnnualDividend", | |
| title="Dividend vs Price", | |
| hover_data=hover_fields, | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig6, use_container_width=True) | |
| else: | |
| st.info("Dividend data is not available.") | |
| except Exception: | |
| st.warning("Dividend vs Price chart could not be generated.") | |
| # Chart 7: Beta Distribution (Histogram) | |
| with st.container(border=True): | |
| try: | |
| fig7 = px.histogram( | |
| df, | |
| x="beta", | |
| nbins=30, | |
| title="Beta Distribution", | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig7, use_container_width=True) | |
| except Exception: | |
| st.warning("Beta distribution chart could not be generated.") | |
| # Chart 8: Exchange Breakdown (Bar Chart) | |
| with st.container(border=True): | |
| try: | |
| if "exchangeShortName" in df.columns: | |
| exchange_counts = df["exchangeShortName"].value_counts().reset_index() | |
| exchange_counts.columns = ["Exchange", "Count"] | |
| fig8 = px.bar( | |
| exchange_counts, | |
| x="Exchange", | |
| y="Count", | |
| title="Exchange Breakdown", | |
| template="plotly_dark" | |
| ) | |
| st.plotly_chart(fig8, use_container_width=True) | |
| else: | |
| st.info("Exchange data is not available.") | |
| except Exception: | |
| st.warning("Exchange breakdown chart could not be generated.") | |
| # Hide default Streamlit style | |
| st.markdown( | |
| """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |